@phyxiusjs/observe
v0.0.1
Published
Observability utilities for manipulating context data
Maintainers
Readme
@phyxiusjs/observe
Observability utilities for manipulating context data
Observe provides a convenient namespace with static functions for adding observability data to contexts created by @phyxiusjs/context. It's a thin layer that makes it easy to set, increment, and track data without directly manipulating context objects.
Installation
npm install @phyxiusjs/observe @phyxiusjs/contextQuick Start
import { context } from "@phyxiusjs/context";
import { observe } from "@phyxiusjs/observe";
await context.scope(
async () => {
// Set operation metadata
observe.set("operation", "user.login");
observe.set("startTime", Date.now());
// Track events as they happen
observe.push("events", { type: "auth.attempt", method: "password" });
// Increment counters
observe.inc("attempts");
// Business logic here...
const user = await authenticateUser(credentials);
// Add more observability data
observe.set("userId", user.id);
observe.push("events", { type: "auth.success", userId: user.id });
observe.set("endTime", Date.now());
},
{ initial: { requestId: "req-123" } },
);API Reference
observe.set(key, value)
Sets a key-value pair in the current context data.
observe.set("operation", "user.login");
observe.set("metadata", { source: "api", version: "v1" });
observe.set("startTime", Date.now());observe.get(key)
Gets a value from the current context data.
const operation = observe.get("operation");
const startTime = observe.get("startTime") as number;observe.push(arrayName, value)
Pushes a value to an array in the context data. Creates the array if it doesn't exist.
observe.push("events", { type: "database.query", table: "users" });
observe.push("errors", new Error("Connection failed"));
observe.push("metrics", { name: "response_time", value: 150 });observe.inc(key, amount?)
Increments a numeric counter. Initializes to the increment amount if the key doesn't exist.
observe.inc("attempts"); // Increment by 1
observe.inc("retries", 3); // Increment by 3
observe.inc("bytes_sent", 1024); // Track cumulative valuesobserve.has(key)
Checks if a key exists in the current context data.
if (observe.has("userId")) {
// User is authenticated
}
if (!observe.has("startTime")) {
observe.set("startTime", Date.now());
}observe.delete(key)
Removes a key from the current context data.
observe.delete("temporaryData");
const wasDeleted = observe.delete("cache"); // returns booleanobserve.all()
Gets all context data as a readonly object.
const allData = observe.all();
console.log(allData.operation, allData.events);Usage Patterns
Request Tracking
await context.scope(
async () => {
observe.set("operation", "api.processPayment");
observe.set("requestId", req.headers["x-request-id"]);
observe.set("userId", req.user?.id);
observe.set("startTime", Date.now());
try {
observe.push("events", { type: "payment.start" });
const result = await processPayment(req.body);
observe.push("events", { type: "payment.success", amount: result.amount });
observe.set("status", "success");
return result;
} catch (error) {
observe.push("events", { type: "payment.error", error: error.message });
observe.set("status", "error");
throw error;
} finally {
observe.set("endTime", Date.now());
observe.set("duration", observe.get("endTime") - observe.get("startTime"));
}
},
{ initial: {} },
);Performance Monitoring
await context.scope(
async () => {
observe.set("operation", "database.bulkInsert");
observe.set("batchSize", records.length);
for (const record of records) {
try {
await insertRecord(record);
observe.inc("successCount");
} catch (error) {
observe.inc("errorCount");
observe.push("errors", { recordId: record.id, error: error.message });
}
}
const successRate = observe.get("successCount") / records.length;
observe.set("successRate", successRate);
},
{ initial: { successCount: 0, errorCount: 0, errors: [] } },
);Distributed Tracing
await context.scope(
async () => {
observe.set("traceId", generateTraceId());
observe.set("operation", "order.process");
observe.push("spans", { name: "order.validate", start: Date.now() });
await validateOrder(order);
observe.push("spans", { name: "order.validate", end: Date.now() });
observe.push("spans", { name: "inventory.reserve", start: Date.now() });
await reserveInventory(order.items);
observe.push("spans", { name: "inventory.reserve", end: Date.now() });
observe.push("spans", { name: "payment.charge", start: Date.now() });
await chargePayment(order.payment);
observe.push("spans", { name: "payment.charge", end: Date.now() });
},
{ initial: { spans: [] } },
);Integration with Context
Observe works directly with any context created by @phyxiusjs/context:
// Typed contexts work perfectly
interface RequestContext {
requestId: string;
operation?: string;
events?: Array<{ type: string; timestamp: number }>;
metrics?: Record<string, number>;
}
await context.scope<RequestContext>(
async () => {
// Type-safe context access
const ctx = context.get<RequestContext>();
console.log(ctx.data.requestId); // string
// observe namespace still works
observe.set("operation", "user.profile.update");
observe.push("events", { type: "profile.start", timestamp: Date.now() });
},
{ initial: { requestId: "req-456" } },
);Context Inheritance
Observe respects context inheritance - child contexts inherit and can modify parent observability data:
await context.scope(
async () => {
observe.set("service", "user-api");
observe.push("trace", { span: "root", operation: "getUser" });
await context.scope(async () => {
// Child can see and modify parent data
observe.push("trace", { span: "child", operation: "validateToken" });
observe.inc("validations");
console.log(observe.get("service")); // "user-api"
});
// Parent sees child modifications
const trace = observe.get("trace") as Array<{ span: string; operation: string }>;
console.log(trace.length); // 2 spans
},
{ initial: {} },
);Error Handling
All observe functions throw an error if no active context is available:
// This will throw "No active context available"
observe.set("key", "value");
// Always use within a context scope
await context.scope(async () => {
observe.set("key", "value"); // Works fine
});Why Observe?
Observe is designed to be a utility namespace - it provides convenient methods for common observability patterns without being opinionated about how you structure your data. It's perfect for:
- Adding observability to existing applications without refactoring
- Incremental adoption - start simple, add more sophisticated patterns later
- Auto-completion - IDE support for discovering available operations
- Consistency - standard way to add observability data across teams
License
MIT © Rodrigo Sasaki
