Spoosh
Plugin DevelopmentAdvanced

Tracing

Emit trace events for devtool visibility

When the devtool plugin is enabled, your plugins can emit trace events to show what they're doing. This helps users understand request flow and debug issues.

Request Tracing (t)

Use context.tracer to emit events during request processing. The convention is to name it t:

function myPlugin(): SpooshPlugin {
  return {
    name: "my-app:my-plugin",
    operations: ["read"],

    middleware: async (context, next) => {
      const t = context.tracer?.("my-app:my-plugin");

      t?.log("Starting processing");

      const cached = getFromCache(context.queryKey);

      if (cached) {
        t?.return("Cache hit");
        return cached;
      }

      t?.log("Cache miss, fetching");
      return next();
    },
  };
}

Methods

MethodWhen to use
logInformational step
returnMiddleware returns early
skipMiddleware skips without processing

Options

All methods accept an optional second parameter:

t?.log("Message", {
  color: "blue",
  diff: { before: oldValue, after: newValue, label: "Data" },
  info: [{ label: "Count", value: 5 }],
});
OptionTypeDescription
color"blue" | "green" | "yellow" | "red"Step color in timeline
diff{ before, after, label? }Show before/after diff view
infoArray<{ label?, value }>Additional info to display

Showing Diffs

Use diff to visualize data transformations:

function transformPlugin(): SpooshPlugin {
  return {
    name: "my-app:transform",
    operations: ["read"],

    middleware: async (context, next) => {
      const t = context.tracer?.("my-app:transform");
      const response = await next();

      if (response.data) {
        const before = response.data;
        const after = transformData(response.data);

        t?.log("Transformed response", {
          diff: { before, after, label: "Response Data" },
        });

        return { ...response, data: after };
      }

      return response;
    },
  };
}

Showing Info

Use info to display additional metadata:

t?.log("Retrying request", {
  color: "yellow",
  info: [
    { label: "Attempt", value: attempt },
    { label: "Max", value: maxRetries },
    { label: "Delay", value: `${delay}ms` },
  ],
});

Event Tracing (et)

Use context.eventTracer for events outside of request lifecycle. The convention is to name it et:

function myPlugin(): SpooshPlugin {
  return {
    name: "my-app:my-plugin",
    operations: [],

    setup(context) {
      const et = context.eventTracer?.("my-app:my-plugin");

      et?.emit("Plugin initialized");

      // Listen for events
      context.eventEmitter.on("someEvent", () => {
        et?.emit("Received someEvent", { color: "blue" });
      });
    },
  };
}

emit Options

et?.emit("Message", {
  color: "green",
  queryKey: "users/123",
  meta: { custom: "data" },
});
OptionTypeDescription
colorstringEvent color in event log
queryKeystringAssociate with specific query
metaobjectAdditional metadata to display

When to Use Each

ScenarioUse
Inside middlewaret
Inside afterResponset
Inside setupet
Inside lifecycle hookset
Event listenerset
Timers / intervalset

Complete Example

function cachePlugin(): SpooshPlugin {
  const cache = new Map();

  return {
    name: "my-app:cache",
    operations: ["read"],

    setup(context) {
      const et = context.eventTracer?.("my-app:cache");
      et?.emit("Cache initialized");

      context.eventEmitter.on("invalidate", (tags) => {
        et?.emit(`Invalidated ${tags.length} tags`, {
          color: "yellow",
          meta: { tags },
        });
      });
    },

    middleware: async (context, next) => {
      const t = context.tracer?.("my-app:cache");
      const cached = cache.get(context.queryKey);

      if (cached && !isStale(cached)) {
        t?.return("Cache hit", {
          color: "green",
          info: [{ label: "Age", value: `${getAge(cached)}ms` }],
        });
        return cached.response;
      }

      t?.log("Cache miss");
      const response = await next();

      if (response.data) {
        cache.set(context.queryKey, { response, timestamp: Date.now() });
        t?.log("Cached response");
      }

      return response;
    },
  };
}

No Devtool? No Problem

When devtool is not installed, context.tracer and context.eventTracer are undefined. Using optional chaining (?.) ensures your plugin works without devtool:

const t = context.tracer?.("my-plugin");
t?.log("This does nothing if devtool is not installed");

On this page