Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ createSiteSetup({
});

// Plumbs spans, traceparent injection, URL redaction, and the
// `commerce_request_duration_ms` histogram into every outbound
// VTEX call. Operation names are derived from the URL via
// `vtexOperationRouter` (overridable per call via `init.operation`).
// canonical `http.client.request.duration` histogram into every
// outbound VTEX call (via `recordCommerceMetric`). Operation names
// are derived from the URL via `vtexOperationRouter` (overridable
// per call via `init.operation`).
setVtexFetch(createVtexFetch());
```

Expand Down
3 changes: 2 additions & 1 deletion blog/__tests__/handlePosts.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BlogPost, SortBy } from "../types";
import { describe, expect, it } from "vitest";
import handlePosts, {
filterPostsByCategory,
filterPostsBySlugs,
Expand All @@ -7,6 +7,7 @@ import handlePosts, {
slicePosts,
sortPosts,
} from "../core/handlePosts";
import type { BlogPost, SortBy } from "../types";

function makePost(overrides: Partial<BlogPost> = {}): BlogPost {
return {
Expand Down
8 changes: 4 additions & 4 deletions blog/__tests__/loaders.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("../core/records", () => ({
getRecordsByPath: vi.fn(),
}));

import { getRecordsByPath } from "../core/records";
import type { BlogPost, Category } from "../types";
import BlogPostItem from "../loaders/BlogPostItem";
import BlogPostPageLoader from "../loaders/BlogPostPage";
import BlogpostListing from "../loaders/BlogpostListing";
import BlogRelatedPostsLoader from "../loaders/BlogRelatedPosts";
import BlogPostPageLoader from "../loaders/BlogPostPage";
import GetCategories from "../loaders/GetCategories";
import BlogPostItem from "../loaders/BlogPostItem";
import type { BlogPost, Category } from "../types";

const mockGetRecords = getRecordsByPath as ReturnType<typeof vi.fn>;

Expand Down
3 changes: 2 additions & 1 deletion blog/__tests__/mod.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, expect, it } from "vitest";
import { configure } from "../mod";

describe("blog module", () => {
it("returns AppDefinition with name 'blog' and manifest", async () => {
const app = await configure({}, async () => undefined);
const app = await configure({}, async () => null);
expect(app.name).toBe("blog");
expect(app.manifest).toBeDefined();
expect(app.state).toEqual({});
Expand Down
2 changes: 1 addition & 1 deletion blog/__tests__/records.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("@decocms/start/cms", () => ({
loadBlocks: vi.fn(),
Expand Down
2 changes: 1 addition & 1 deletion blog/core/handlePosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const sortPosts = (blogPosts: BlogPost[], sortBy: SortBy): BlogPost[] =>
const sortMethod = (parts[0] in blogPosts[0] ? parts[0] : "date") as keyof BlogPost;
const sortOrder = VALID_SORT_ORDERS.includes(parts[1]) ? parts[1] : "desc";

return blogPosts.toSorted((a, b) => {
return [...blogPosts].sort((a, b) => {
if (!a[sortMethod] && !b[sortMethod]) return 0;
if (!a[sortMethod]) return 1;
if (!b[sortMethod]) return -1;
Expand Down
9 changes: 6 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
"access": "public"
},
"peerDependencies": {
"@decocms/start": ">=5.3.0",
"@decocms/start": ">=6.6.0",
"@tanstack/react-query": ">=5",
"@tanstack/react-start": ">=1",
"algoliasearch": "^5",
Expand All @@ -149,7 +149,7 @@
},
"devDependencies": {
"@biomejs/biome": "^2.4.7",
"@decocms/start": "5.3.0",
"@decocms/start": "6.6.1",
"@semantic-release/exec": "^7.1.0",
"@tanstack/react-query": "^5.90.21",
"@types/react": "^19.0.0",
Expand Down
5 changes: 3 additions & 2 deletions shopify/utils/__tests__/instrumentedFetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("createShopifyFetch", () => {
});
});

it("emits commerce_request_duration_ms with provider=shopify on success", async () => {
it("emits http.client.request.duration with provider=shopify on success", async () => {
const { calls, meter } = captureHistogram();
configureMeter(meter);

Expand All @@ -44,10 +44,11 @@ describe("createShopifyFetch", () => {
await fetchFn("https://store.myshopify.com/api/2025-04/graphql.json", { method: "POST" });

expect(calls).toHaveLength(1);
expect(calls[0].name).toBe("http.client.request.duration");
expect(calls[0].attrs).toMatchObject({
provider: "shopify",
operation: "storefront.graphql",
status_code: "200",
status_class: "2xx",
});
});

Expand Down
16 changes: 7 additions & 9 deletions shopify/utils/instrumentedFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
* traceparent, URL redaction).
* 2. `shopifyOperationRouter` as the URL fallback for non-GraphQL
* and unnamed-GraphQL calls.
* 3. An `onComplete` that records `commerce_request_duration_ms`
* with `provider: "shopify"`.
* 3. An `onComplete` that records the canonical
* `http.client.request.duration` histogram (via the framework's
* `recordCommerceMetric(...)` helper) with `provider: "shopify"`.
*
* Sites do:
*
Expand All @@ -26,11 +27,9 @@ import {
createInstrumentedFetch,
type InstrumentedFetch,
} from "@decocms/start/sdk/instrumentedFetch";
import { getMeter } from "@decocms/start/sdk/observability";
import { recordCommerceMetric } from "@decocms/start/sdk/observability";
import { shopifyOperationRouter } from "./operationRouter";

const HISTOGRAM_NAME = "commerce_request_duration_ms";

export interface CreateShopifyFetchOptions {
baseFetch?: typeof fetch;
disableHistogram?: boolean;
Expand All @@ -45,12 +44,11 @@ export function createShopifyFetch(options: CreateShopifyFetchOptions = {}): Ins
onComplete: disableHistogram
? undefined
: ({ operation, status, durationMs, cached }) => {
const meter = getMeter();
meter?.histogramRecord?.(HISTOGRAM_NAME, durationMs, {
recordCommerceMetric(durationMs, {
provider: "shopify",
operation,
status_code: String(status),
cached: cached ? "true" : "false",
status_class: `${Math.floor(status / 100)}xx`,
cached,
});
},
});
Expand Down
2 changes: 1 addition & 1 deletion vtex/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function configureVtex(config: VtexConfig) {
/**
* Override the fetch function used by all VTEX client calls.
* Pass an `InstrumentedFetch` to get spans, traceparent injection,
* URL redaction, and the `commerce_request_duration_ms` histogram —
* URL redaction, and the canonical `http.client.request.duration` histogram —
* use the pre-wired `createVtexFetch()` factory:
*
* ```ts
Expand Down
19 changes: 10 additions & 9 deletions vtex/utils/__tests__/instrumentedFetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*
* - The URL router is plumbed through so unannotated callsites get
* semantic span operations + histogram labels.
* - The `commerce_request_duration_ms` histogram is recorded with the
* right labels on every call.
* - The canonical `http.client.request.duration` histogram is recorded
* with the right labels on every call (via the framework's
* `recordCommerceMetric` helper).
* - `disableHistogram: true` opts out cleanly.
* - A caller's explicit `init.operation` wins over the URL router
* (delegating to the framework, but worth asserting at this seam).
Expand Down Expand Up @@ -50,7 +51,7 @@ describe("createVtexFetch", () => {
});
});

it("records commerce_request_duration_ms with provider/operation/status labels on success", async () => {
it("records http.client.request.duration with provider/operation/status labels on success", async () => {
const { calls, meter } = captureHistogram();
configureMeter(meter);

Expand All @@ -60,12 +61,12 @@ describe("createVtexFetch", () => {
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");

expect(calls).toHaveLength(1);
expect(calls[0].name).toBe("commerce_request_duration_ms");
expect(calls[0].name).toBe("http.client.request.duration");
expect(calls[0].attrs).toMatchObject({
provider: "vtex",
operation: "sessions.get",
status_code: "200",
cached: "false",
status_class: "2xx",
cached: false,
});
expect(calls[0].value).toBeGreaterThanOrEqual(0);
});
Expand Down Expand Up @@ -109,10 +110,10 @@ describe("createVtexFetch", () => {

await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");

expect(calls[0].attrs.cached).toBe("true");
expect(calls[0].attrs.cached).toBe(true);
});

it("emits status_code reflecting the actual response status", async () => {
it("emits status_class derived from the actual response status", async () => {
const { calls, meter } = captureHistogram();
configureMeter(meter);

Expand All @@ -121,7 +122,7 @@ describe("createVtexFetch", () => {

await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");

expect(calls[0].attrs.status_code).toBe("503");
expect(calls[0].attrs.status_class).toBe("5xx");
});

it("skips histogram emission when disableHistogram is true", async () => {
Expand Down
25 changes: 11 additions & 14 deletions vtex/utils/instrumentedFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
* 2. The `vtexOperationRouter` URL→operation mapping so unannotated
* callsites still get semantic span names + histogram labels.
* 3. An `onComplete` callback that records every call into the
* `commerce_request_duration_ms` histogram via the meter
* configured by `instrumentWorker(...)` in
* canonical `http.client.request.duration` histogram via the
* framework's `recordCommerceMetric(...)` helper in
* `@decocms/start/sdk/observability` — `provider`, `operation`,
* `status_code`, and `cached` labels.
* `status_class`, and `cached` labels.
*
* Sites opt in once at startup:
*
Expand All @@ -34,11 +34,9 @@ import {
createInstrumentedFetch,
type InstrumentedFetch,
} from "@decocms/start/sdk/instrumentedFetch";
import { getMeter } from "@decocms/start/sdk/observability";
import { recordCommerceMetric } from "@decocms/start/sdk/observability";
import { vtexOperationRouter } from "./operationRouter";

const HISTOGRAM_NAME = "commerce_request_duration_ms";

export interface CreateVtexFetchOptions {
/**
* Underlying fetch to wrap. Defaults to `globalThis.fetch`.
Expand All @@ -48,10 +46,10 @@ export interface CreateVtexFetchOptions {
*/
baseFetch?: typeof fetch;
/**
* Disable the `commerce_request_duration_ms` histogram emission.
* The framework's span and structured logs still emit. Useful when
* the consumer wants to record its own histogram with a custom shape.
* Default: false.
* Disable the `http.client.request.duration` histogram emission for
* VTEX calls. The framework's span and structured logs still emit.
* Useful when the consumer wants to record its own histogram with a
* custom shape. Default: false.
*/
disableHistogram?: boolean;
}
Expand All @@ -69,12 +67,11 @@ export function createVtexFetch(options: CreateVtexFetchOptions = {}): Instrumen
onComplete: disableHistogram
? undefined
: ({ operation, status, durationMs, cached }) => {
const meter = getMeter();
meter?.histogramRecord?.(HISTOGRAM_NAME, durationMs, {
recordCommerceMetric(durationMs, {
provider: "vtex",
operation,
status_code: String(status),
cached: cached ? "true" : "false",
status_class: `${Math.floor(status / 100)}xx`,
cached,
});
},
});
Expand Down
Loading