feat(core-shared): add withSpan higher-order helper
This commit is contained in:
62
packages/core-shared/src/instrumentation/with-span.test.ts
Normal file
62
packages/core-shared/src/instrumentation/with-span.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { withSpan } from "@/instrumentation/with-span";
|
||||
import type { ITracer, ISpan, SpanOpts } from "@/instrumentation/tracer.interface";
|
||||
|
||||
function makeRecordingTracer() {
|
||||
const calls: SpanOpts[] = [];
|
||||
const tracer: ITracer = {
|
||||
startSpan: vi.fn(async (opts, fn) => {
|
||||
calls.push(opts);
|
||||
const span: ISpan = { setAttribute: () => {}, setStatus: () => {} };
|
||||
return fn(span);
|
||||
}),
|
||||
};
|
||||
return { tracer, calls };
|
||||
}
|
||||
|
||||
describe("withSpan", () => {
|
||||
it("wraps fn with a span using static opts", async () => {
|
||||
const { tracer, calls } = makeRecordingTracer();
|
||||
const fn = async (a: number, b: number) => a + b;
|
||||
const wrapped = withSpan(tracer, { name: "test.add", op: "use-case" }, fn);
|
||||
const result = await wrapped(2, 3);
|
||||
expect(result).toBe(5);
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]).toEqual({ name: "test.add", op: "use-case" });
|
||||
});
|
||||
|
||||
it("wraps fn with span opts derived from args (function form)", async () => {
|
||||
const { tracer, calls } = makeRecordingTracer();
|
||||
const fn = async (id: string) => `result-${id}`;
|
||||
const wrapped = withSpan(
|
||||
tracer,
|
||||
([id]) => ({ name: "test.byId", op: "repository", attributes: { id } }),
|
||||
fn,
|
||||
);
|
||||
const result = await wrapped("abc");
|
||||
expect(result).toBe("result-abc");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]).toEqual({
|
||||
name: "test.byId",
|
||||
op: "repository",
|
||||
attributes: { id: "abc" },
|
||||
});
|
||||
});
|
||||
|
||||
it("propagates errors thrown by fn", async () => {
|
||||
const { tracer } = makeRecordingTracer();
|
||||
const wrapped = withSpan(tracer, { name: "test.err" }, async () => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
await expect(wrapped()).rejects.toThrow("boom");
|
||||
});
|
||||
|
||||
it("preserves identity across multiple invocations (closure stable)", async () => {
|
||||
const { tracer, calls } = makeRecordingTracer();
|
||||
const wrapped = withSpan(tracer, { name: "test.same" }, async (n: number) => n);
|
||||
await wrapped(1);
|
||||
await wrapped(2);
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(calls.every((c) => c.name === "test.same")).toBe(true);
|
||||
});
|
||||
});
|
||||
12
packages/core-shared/src/instrumentation/with-span.ts
Normal file
12
packages/core-shared/src/instrumentation/with-span.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ITracer, SpanOpts } from "./tracer.interface";
|
||||
|
||||
export function withSpan<Args extends unknown[], R>(
|
||||
tracer: ITracer,
|
||||
opts: SpanOpts | ((args: Args) => SpanOpts),
|
||||
fn: (...args: Args) => Promise<R>,
|
||||
): (...args: Args) => Promise<R> {
|
||||
return (...args) => {
|
||||
const resolved = typeof opts === "function" ? opts(args) : opts;
|
||||
return tracer.startSpan(resolved, () => fn(...args));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user