feat(observability): URL routers + per-call operation names + commerce histograms#48
Merged
Merged
Conversation
release: promote 1.14.0-next.1 to stable
Unlocks the operation API on `createInstrumentedFetch` (init.operation / defaultOperation / resolveOperation) shipped in deco-start PR #179, which the upcoming VTEX + Shopify URL routers and per-call operation overrides in this PR depend on. - devDep: ^2.5.0 -> 5.3.0-rc.0 (exact pin to the rc we're validating) - peerDep: >=0.19.0 -> >=5.3.0-rc.0 (consumers must opt into the rc before installing the @decocms/apps next channel that depends on it) Co-authored-by: Cursor <cursoragent@cursor.com>
Pure `(url, method) => operation | undefined` function pluggable into
`createInstrumentedFetch`'s `resolveOperation` option (shipped in
@decocms/start 5.3.0-rc.0). The resolved string becomes the span suffix
(`vtex.<operation>`), the `fetch.operation` span attribute, and the
histogram label downstream — so it's deliberately low-cardinality and
stable across deploys.
Covers the VTEX API surface this repo actually exercises:
- Intelligent Search (product_search, facets, top_searches, suggestions,
correction, banners, autocomplete) — endpoint becomes the suffix
- Checkout / orderForm — POST/PATCH/DELETE on `/items` are differentiated
into add / update / remove; coupons, profile, shippingData, paymentData,
simulation, regions, postal-code are split off; the singleton
orderForm/{id} root distinguishes GET vs PATCH
- Sessions (get vs update by method), segments
- Catalog System — pagetype, crossselling.{type}, products.variations,
products.search, facets.search, category.tree, sku, specification,
brand, falls back to catalog.other
- Masterdata (entity name encoded into the operation suffix)
- OMS (orders, orders.cancel, orders.pvt)
- VTEX ID (logout, authentication.start/validate, user, vtexid.other)
- IO GraphQL (POST to `_v/private/graphql/v1`), IO segment, sitemap.xml,
events, license-manager
22 vitest cases cover the matchers + edge cases (unparseable URLs,
case-insensitive methods, query-string stripping, fallback to
`undefined`). Callers that need finer granularity than the URL can
express (same URL, semantically different ops) override per-call via
`init.operation`, which always wins over the router.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds two paired primitives for Shopify outbound observability:
- `shopify/utils/operationRouter.ts` — same shape as the VTEX router;
pluggable into `createInstrumentedFetch.resolveOperation`. Maps the
storefront / admin GraphQL endpoints to `storefront.graphql` /
`admin.graphql` and covers the small REST surface (products, orders,
customers, inventory, checkouts, cart).
- `shopify/utils/graphqlOperationName.ts` — extracts the semantic
operation name from a GraphQL document (`query Foo { ... }` →
`"Foo"`). Strips block strings, string literals, and `# comments`
before matching so `query` / `mutation` / `subscription` keywords
inside docstrings/comments don't false-positive. Returns
`undefined` for anonymous or multi-operation documents (caller must
disambiguate via explicit name).
Wired into `createGraphqlClient` so every Shopify GraphQL call stamps
`init.operation = <OperationName>` on the outbound fetch. The
framework reads it via the operation API shipped in @decocms/start
5.3.0-rc.0 and produces spans named `shopify.<OperationName>` (e.g.
`shopify.ProductDetails`) instead of the generic
`shopify.storefront.graphql`. The URL router still applies as a
fallback when the extractor can't derive a name.
33 vitest cases cover both helpers including comment/string handling
and a real-world Shopify storefront query shape.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pre-wired `InstrumentedFetch` factories that bundle the three pieces sites would otherwise have to assemble by hand: 1. `createInstrumentedFetch` from @decocms/start (spans, traceparent injection, URL redaction, cache-header span attributes). 2. The provider's URL router as `resolveOperation`, so unannotated callsites still get semantic span operations + histogram labels. 3. An `onComplete` callback that records every call into the `commerce_request_duration_ms` histogram via the meter configured by `instrumentWorker(...)`. Labels: `provider`, `operation`, `status_code`, `cached`. Sites opt in with one line at startup: setVtexFetch(createVtexFetch()); setShopifyFetch(createShopifyFetch()); `disableHistogram: true` opts out of metric emission while keeping spans + logs. `baseFetch` lets callers wrap a custom fetch (cookie passthrough, retry, proxy) and add instrumentation on top. Both factories + the underlying URL routers are re-exported from `@decocms/apps/vtex` and `@decocms/apps/shopify` for site consumption. `extractGraphqlOperationName` is also exposed so sites can name their own GraphQL clients consistently. 10 vitest cases cover the wiring: histogram emission with correct labels, URL-router operation propagation, explicit `init.operation` overrides, `x-cache: HIT` -> `cached=true`, status code propagation, `disableHistogram` opt-out, and stripping `operation` from the init before it reaches `baseFetch`. Co-authored-by: Cursor <cursoragent@cursor.com>
Routes all server-side ad-hoc fetches in vtex/ through the `setVtexFetch`-controlled `_fetch` instead of raw `globalThis.fetch`, so sites that opt into the instrumented factory (`createVtexFetch()`) get spans + commerce_request_duration_ms metrics + traceparent injection on every outbound VTEX call. Adds two API surface widenings on `vtex/client.ts`: - `setVtexFetch` now accepts `typeof fetch | InstrumentedFetch` (was `typeof fetch`). A plain fetch still works for uninstrumented test/dev setups; an `InstrumentedFetch` unlocks `init.operation` propagation. - New `getVtexFetch(): InstrumentedFetch` accessor for callsites that can't shape themselves into the `vtexFetch*` helpers — FormData uploads, .aspx legacy endpoints, generic proxies, etc. Callers stamp a per-call operation string via `init.operation`. - `vtexFetchResponse / vtexFetch / vtexCachedFetch / vtexFetchWithCookies` init parameter widened to `InstrumentedFetchInit` so `operation` flows through them too. Migrated callsites with explicit operations: - `vtex/utils/authHelpers.ts` performVtexLogout -> vtexid.logout - `vtex/utils/proxy.ts` proxyToVtex -> URL-router - `vtex/utils/proxy.ts` createVtexCheckoutProxy -> URL-router - `vtex/actions/misc.ts` notifyMe (AviseMe.aspx) -> notifyme - `vtex/actions/misc.ts` sendEvent (sp.vtex.com) -> analytics.event - `vtex/actions/newsletter.ts` subscribe -> newsletter.subscribe - `vtex/actions/analytics/sendEvent.ts` IS event -> analytics.event - `vtex/actions/profile.ts` updateProfileFromRequest -> io.graphql.UpdateProfile - `vtex/actions/masterData.ts` uploadAttachment -> masterdata.attachment.upload Untouched (intentional): - Client-side React hooks (`vtex/hooks/use*.ts`) — they run in the browser against same-origin `/api/...` which is proxied by `proxyToVtex` / `createVtexCheckoutProxy` on the server, so they inherit observability from the proxy span. Wiring an instrumented fetch in the browser yields no tracer / no metrics — wasted layer. - `resend/actions/send.ts`, `website/loaders/fonts/googleFonts.ts` — non-VTEX hosts, low traffic; left as raw fetch for now to keep PR scope focused. Tracked as follow-ups in observability docs. Co-authored-by: Cursor <cursoragent@cursor.com>
- Recipe in the Setup section shows the one-liner each (`createVtexFetch()` / `createShopifyFetch()`) plus the rc-version requirement on `@decocms/start@>=5.3.0-rc.0`. - Subpath table now lists the new barrel exports (`createVtexFetch`, `vtexOperationRouter`, `createShopifyFetch`, `shopifyOperationRouter`, `extractGraphqlOperationName`, `getVtexFetch`). - knip.json: drop `@decocms/start` from `ignoreDependencies` — it's now an actively-imported peer dep (was a knip workaround when the package was only referenced in JSDoc). Co-authored-by: Cursor <cursoragent@cursor.com>
4 tasks
|
🎉 This PR is included in version 1.15.0-next.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds end-to-end semantic operation naming on outbound VTEX + Shopify fetches and emits a provider-labelled commerce duration histogram. Builds on the operation API shipped in
@decocms/start@5.3.0-rc.0(deco-start#179).After this lands, every outbound commerce call from a storefront produces:
vtex.<operation>/shopify.<operation>(e.g.vtex.checkout.orderform.items.add,shopify.ProductDetails) instead of a genericvtex.fetch.fetch.operationattribute on the span so the same string is queryable independently of span name.commerce_request_duration_mshistogram record with labels{ provider, operation, status_code, cached }.traceparentinjection (already in the framework).Zero behavioral change for sites that don't opt in —
setVtexFetch(createVtexFetch())is the one-line opt-in.What's in here
chore(deps): bump@decocms/startdevDep →5.3.0-rc.0, peerDep →>=5.3.0-rc.0.feat(vtex):vtex/utils/operationRouter.ts— pure(url, method) => operation | undefined. Covers IS, checkout/orderForm (incl. POST/PATCH/DELETE differentiation on/items), sessions, segments, catalog, masterdata (entity-suffixed), OMS, vtexid, IO GraphQL, sitemap. 22 vitest cases.feat(shopify):shopify/utils/operationRouter.ts+graphqlOperationName.ts— URL router + GraphQL document parser. Names spansshopify.<OperationName>(extracted fromquery Foo { ... }) instead of the genericshopify.storefront.graphql. Wired intocreateGraphqlClient. 11 vitest cases.feat(factories):createVtexFetch()/createShopifyFetch()— pre-wiredInstrumentedFetchfactories bundlingcreateInstrumentedFetch+ URL router +onComplete: commerce_request_duration_ms. 10 vitest cases.refactor(vtex): funnel 9 server-side ad-hoc fetches through the configured_fetch:performVtexLogout→vtexid.logoutproxyToVtex/createVtexCheckoutProxy→ URL-routernotifyMe/subscribe(.aspx) →notifyme/newsletter.subscribesendEvent× 2 (sp.vtex.com) →analytics.eventupdateProfileFromRequest→io.graphql.UpdateProfileuploadAttachment→masterdata.attachment.uploadAdds
getVtexFetch()accessor + widenssetVtexFetchto acceptInstrumentedFetch | typeof fetch+ widensvtexFetch*helpers to takeInstrumentedFetchInitso theoperationfield threads through.docs(readme): setup recipe + subpath table.Out of scope (follow-ups)
resend/actions/send.ts) and Google Fonts (website/loaders/fonts/googleFonts.ts) raw fetches — non-VTEX/non-Shopify hosts, low traffic; left as raw fetch to keep PR scope focused. Easy follow-up if/when needed.vtex/hooks/use*.ts) intentionally untouched. They run in the browser against same-origin/api/..., which is proxied server-side byproxyToVtex/createVtexCheckoutProxy(now instrumented). Wiring an instrumented fetch in the browser gives no tracer / no metrics — wasted layer.main:vtex/__tests__/client-segment-cookie.test.tshas 2 cases that fail under Node 22 / undici (which treatscookieas a forbidden request header onnew Request()). Not introduced by this PR — fails identically with the dep bump stashed. Worth a separatefix/segment-cookie-testto rewrite the fixture before promotingnext → main.Test plan
npm run typecheck— cleannpm run lint— cleannpm test— 411 / 413 passing; the 2 failures are the pre-existing cookie tests called out abovenpx knip— no new findings (and removed one staleignoreDependenciesentry)1.x.0-next.Xfrom this branchcommerce_request_duration_mshistogram shows up in ClickHouse / GrafanaCoordinated release path
This is the apps-start half of a three-PR observability rollout:
POST /v1/metrics+ OTLP metrics DDLs. Merged + deployed.5.3.0-rc.0on the@rcdist-tag (deco-start'snextbranch is currently blocked by unrelated unfinished work). Will be promoted to5.3.0onmainonce this rc soaks.next, semantic-release publishes@decocms/apps@1.x.0-next.Xon the@nexttag. Then bump a real storefront to both prereleases and validate end-to-end.Made with Cursor
Summary by cubic
Adds semantic operation names and a
commerce_request_duration_mshistogram for all outbound VTEX and Shopify calls. Provides pre-wired fetch factories and URL routers; Shopify GraphQL calls are named per operation.New Features
createVtexFetch()andcreateShopifyFetch()pre-wire spans,traceparent, URL redaction, and a histogram with{ provider, operation, status_code, cached }.vtexOperationRouterandshopifyOperationRoutermap URLs to low-cardinality operations.extractGraphqlOperationNamestampsinit.operation, so spans becomeshopify.<OperationName>; wired intocreateGraphqlClient.getVtexFetch()accessor; routed ad-hoc server calls through the configured fetch;setVtexFetchnow acceptsInstrumentedFetch | fetch; VTEX helpers acceptInstrumentedFetchInitsooperationpropagates.Migration
@decocms/start@>=5.3.0-rc.0.setVtexFetch(createVtexFetch())setShopifyFetch(createShopifyFetch())Written for commit 24d0502. Summary will update on new commits. Review in cubic