life
Version:
Life.js is the first fullstack framework to build agentic web applications. It is minimal, extensible, and typesafe. Well, everything you love.
1,231 lines (1,216 loc) • 39 kB
JavaScript
import {
AsyncQueue,
deepClone
} from "./chunk-D2T23PCX.mjs";
import {
attempt,
deserializeResult,
failure,
isLifeError,
isResult,
lifeError,
lifeErrorFromObject,
lifeErrorToObject,
serializeResult,
success
} from "./chunk-ZHBK6UTM.mjs";
import {
__name
} from "./chunk-2D3UJWOA.mjs";
// shared/canon/serialize.ts
import { deserializeError, isErrorLike, serializeError } from "serialize-error";
import superjson from "superjson";
import { ZodError, z } from "zod";
superjson.registerCustom(
{
isApplicable: /* @__PURE__ */ __name((v) => isLifeError(v), "isApplicable"),
// Using superjson.serialize ensures that 'err.cause' gets serialized properly
serialize: /* @__PURE__ */ __name((err) => superjson.serialize(lifeErrorToObject(err)), "serialize"),
deserialize: /* @__PURE__ */ __name((data) => lifeErrorFromObject(superjson.deserialize(data)), "deserialize")
},
"LifeError"
);
superjson.registerCustom(
{
isApplicable: /* @__PURE__ */ __name((v) => v instanceof ZodError, "isApplicable"),
serialize: /* @__PURE__ */ __name((err) => err.issues, "serialize"),
deserialize: /* @__PURE__ */ __name((data) => new ZodError(data), "deserialize")
},
"ZodError"
);
superjson.registerCustom(
{
isApplicable: /* @__PURE__ */ __name((v) => isErrorLike(v), "isApplicable"),
serialize: /* @__PURE__ */ __name((err) => serializeError(err), "serialize"),
deserialize: /* @__PURE__ */ __name((data) => deserializeError(data), "deserialize")
},
"Error"
);
superjson.registerCustom(
{
isApplicable: /* @__PURE__ */ __name((v) => isResult(v), "isApplicable"),
serialize: /* @__PURE__ */ __name((result) => serializeResult(result), "serialize"),
deserialize: /* @__PURE__ */ __name((data) => deserializeResult(data), "deserialize")
},
"OperationResult"
);
var serializablePrimitivesSchema = z.union([
z.string(),
z.number(),
z.boolean(),
z.null(),
z.undefined(),
z.bigint(),
z.date(),
z.instanceof(RegExp),
z.instanceof(Error),
z.instanceof(URL),
z.instanceof(ArrayBuffer),
z.instanceof(Int8Array),
z.instanceof(Uint8Array),
z.instanceof(Uint8ClampedArray),
z.instanceof(Int16Array),
z.instanceof(Uint16Array),
z.instanceof(Int32Array),
z.instanceof(Uint32Array),
z.instanceof(Float32Array),
z.instanceof(Float64Array),
z.instanceof(BigInt64Array),
z.instanceof(BigUint64Array)
]);
var serializableValueSchema = z.lazy(
() => z.union([
serializablePrimitivesSchema,
z.array(serializableValueSchema),
z.set(serializableValueSchema),
z.map(z.any(), serializableValueSchema),
z.record(z.string(), serializableValueSchema)
])
);
var serialize = /* @__PURE__ */ __name((value) => attempt(() => superjson.serialize(value)), "serialize");
var deserialize = /* @__PURE__ */ __name((value) => {
if (!value) return success(value);
return attempt(() => superjson.deserialize(value));
}, "deserialize");
// shared/canon/stringify.ts
function stableDeepStringify(data, sortArrays, opts) {
if (!opts) opts = {};
if (typeof opts === "function") opts = { cmp: opts };
var cycles = typeof opts.cycles === "boolean" ? opts.cycles : false;
var cmp = opts.cmp && /* @__PURE__ */ ((f) => (node) => (a, b) => {
var aobj = { key: a, value: node[a] };
var bobj = { key: b, value: node[b] };
return f(aobj, bobj);
})(opts.cmp);
var seen = [];
return (/* @__PURE__ */ __name((function stringify_(node) {
if (node && node.toJSON && typeof node.toJSON === "function") {
node = node.toJSON();
}
if (node === void 0) return "";
if (typeof node == "number") return isFinite(node) ? "" + node : "null";
if (typeof node !== "object") return JSON.stringify(node);
var i, out;
if (Array.isArray(node)) {
const items = node.map((item) => stringify_(item) || "null");
if (sortArrays) items.sort();
return "[" + items.join(",") + "]";
}
if (node === null) return "null";
if (seen.indexOf(node) !== -1) {
if (cycles) return JSON.stringify("__cycle__");
throw new TypeError("Converting circular structure to JSON");
}
var seenIndex = seen.push(node) - 1;
var keys = Object.keys(node).sort(cmp && cmp(node));
out = "";
for (i = 0; i < keys.length; i++) {
var key = keys[i];
var value = stringify_(node[key]);
if (!value) continue;
if (out) out += ",";
out += JSON.stringify(key) + ":" + value;
}
seen.splice(seenIndex, 1);
return "{" + out + "}";
}), "stringify_"))(data);
}
__name(stableDeepStringify, "stableDeepStringify");
var stringify = /* @__PURE__ */ __name((value, sortArrays = false) => {
const [err, res] = serialize(value);
if (err) return failure(err);
return attempt(() => stableDeepStringify(res, sortArrays));
}, "stringify");
var parse = /* @__PURE__ */ __name((value) => {
const [err, res] = attempt(() => JSON.parse(value));
if (err) return failure(err);
return deserialize(res);
}, "parse");
// shared/canon/equal.ts
var equal = /* @__PURE__ */ __name((a, b) => {
const [err1, data1] = stringify(a, true);
if (err1) return failure(err1);
const [err2, data2] = stringify(b, true);
if (err2) return failure(err2);
return success(data1 === data2);
}, "equal");
// shared/canon/equal-schema.ts
import z2 from "zod";
var equalSchema = /* @__PURE__ */ __name((a, b) => {
const [errJsonA, jsonA] = attempt(() => z2.toJSONSchema(a, { unrepresentable: "any" }));
if (errJsonA) return failure(errJsonA);
const [errJsonB, jsonB] = attempt(() => z2.toJSONSchema(b, { unrepresentable: "any" }));
if (errJsonB) return failure(errJsonB);
return equal(jsonA, jsonB);
}, "equalSchema");
// shared/canon/murmur.ts
import MurmurHash3 from "imurmurhash";
var murmur3 = /* @__PURE__ */ __name((value) => {
try {
const [err, data] = stringify(value);
if (err) return failure(err);
const [errHash, hashNumber] = attempt(() => MurmurHash3(data).result());
if (errHash) return failure(errHash);
const hash = hashNumber.toString(16);
return success(hash);
} catch (error) {
return failure({ code: "Unknown", cause: error });
}
}, "murmur3");
// shared/canon/sha256.ts
var sha256 = /* @__PURE__ */ __name(async (value) => {
try {
const [err, data] = stringify(value);
if (err) return failure(err);
const hashedData = new TextEncoder().encode(data);
const hashBuffer = await crypto.subtle.digest("SHA-256", hashedData);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
return success(hash);
} catch (error) {
return failure({ code: "Unknown", cause: error });
}
}, "sha256");
// shared/canon/index.ts
var canon = {
equal,
equalSchema,
serialize,
deserialize,
sha256,
stringify,
parse,
murmur3
};
// shared/nanoseconds.ts
var hrtimeBigint = (() => {
if (typeof globalThis.process?.hrtime?.bigint === "function") {
return () => globalThis.process.hrtime.bigint();
}
if (typeof globalThis.process?.hrtime === "function") {
return () => {
const [s, n] = globalThis.process.hrtime();
return BigInt(s) * 1000000000n + BigInt(n);
};
}
if (typeof globalThis.performance?.now === "function") {
let last = 0n;
return () => {
const ns2 = BigInt(Math.floor(globalThis.performance.now() * 1e6));
if (ns2 > last) last = ns2;
return last;
};
}
return () => BigInt(Date.now()) * 1000000n;
})();
var ns = {
/**
* Returns the current Unix timestamp in nanoseconds
* @returns BigInt (ns since epoch)
*/
now() {
return hrtimeBigint();
},
/**
* Returns the duration between the current time and the given start time
* @param start - BigInt (ns since epoch)
* @returns BigInt (ns)
*/
since(n) {
if (!n) return 0n;
return ns.now() - n;
},
/**
* Converts nanoseconds to milliseconds
* @param ns - BigInt (ns)
* @returns number (ms)
*/
toMs(n) {
if (!n) return 0n;
return n / 1000000n;
}
};
// telemetry/helpers/otel-id.ts
var SPAN_ID_BYTES = 8;
var TRACE_ID_BYTES = 16;
var LOG_ID_BYTES = 16;
var METRIC_ID_BYTES = 16;
var SHARED_CHAR_CODES_ARRAY = new Array(32);
function getOtelIdGenerator(bytes) {
return /* @__PURE__ */ __name(function generateId() {
for (let i = 0; i < bytes * 2; i++) {
SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48;
if (SHARED_CHAR_CODES_ARRAY[i] >= 58) {
SHARED_CHAR_CODES_ARRAY[i] += 39;
}
}
return String.fromCharCode.apply(null, SHARED_CHAR_CODES_ARRAY.slice(0, bytes * 2));
}, "generateId");
}
__name(getOtelIdGenerator, "getOtelIdGenerator");
var generateSpanId = getOtelIdGenerator(SPAN_ID_BYTES);
var generateTraceId = getOtelIdGenerator(TRACE_ID_BYTES);
var generateLogId = getOtelIdGenerator(LOG_ID_BYTES);
var generateMetricId = getOtelIdGenerator(METRIC_ID_BYTES);
// telemetry/helpers/register-consumer.ts
var registerConsumer = /* @__PURE__ */ __name((consumer, list) => {
const queue = new AsyncQueue();
list.push({ instance: consumer, queue });
consumer.start(queue);
let unregistered = false;
return () => {
if (unregistered) return;
const index = list.findIndex((c) => c.instance === consumer);
if (index !== -1) {
list[index]?.queue.stop();
list.splice(index, 1);
unregistered = true;
}
};
}, "registerConsumer");
// telemetry/helpers/strip-ansi.ts
var regex = new RegExp(
[
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
].join("|"),
"g"
);
function stripAnsi(string) {
if (typeof string !== "string") {
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
}
return string.replace(regex, "");
}
__name(stripAnsi, "stripAnsi");
// telemetry/schemas.ts
import z3 from "zod";
var telemetryAttributeSchema = z3.record(z3.string(), z3.unknown());
var telemetryResourceSchema = z3.object({
environment: z3.enum(["development", "production", "staging", "test"]),
lifeVersion: z3.string()
}).and(
z3.discriminatedUnion("platform", [
z3.object({
platform: z3.literal("node"),
isCi: z3.boolean(),
nodeVersion: z3.string(),
osName: z3.string(),
osVersion: z3.string(),
cpuCount: z3.number(),
cpuArchitecture: z3.string(),
schemaVersion: z3.string().prefault("1")
}),
z3.object({
platform: z3.literal("browser"),
deviceType: z3.enum([
"desktop",
"mobile",
"tablet",
"wearable",
"smarttv",
"console",
"xr",
"embedded",
"unknown"
]),
deviceBrand: z3.string(),
deviceModel: z3.string(),
osName: z3.string(),
osVersion: z3.string(),
cpuArchitecture: z3.enum([
"ia32",
"ia64",
"amd64",
"arm",
"arm64",
"armhf",
"avr",
"avr32",
"irix",
"irix64",
"mips",
"mips64",
"68k",
"pa-risc",
"ppc",
"sparc",
"sparc64",
"alpha",
"unknown"
]).optional(),
browserUserAgent: z3.string(),
browserName: z3.string(),
browserVersion: z3.string(),
browserEngine: z3.enum([
"Amaya",
"ArkWeb",
"Blink",
"EdgeHTML",
"Flow",
"Gecko",
"Goanna",
"iCab",
"KHTML",
"LibWeb",
"Links",
"Lynx",
"NetFront",
"NetSurf",
"Presto",
"Servo",
"Tasman",
"Trident",
"w3m",
"WebKit",
"unknown"
]),
isBot: z3.boolean(),
isAiBot: z3.boolean(),
schemaVersion: z3.string().prefault("1")
})
])
);
var HEX_LOWER_RE = /^[0-9a-f]+$/;
function createOtelHexIdSchema(bytes) {
const len = bytes * 2;
return z3.string().length(len, `expected ${len} lowercase hex chars`).regex(HEX_LOWER_RE, "must be lowercase hex [0-9a-f]");
}
__name(createOtelHexIdSchema, "createOtelHexIdSchema");
var telemetryTraceIdSchema = createOtelHexIdSchema(TRACE_ID_BYTES);
var telemetryLogIdSchema = createOtelHexIdSchema(LOG_ID_BYTES);
var telemetrySpanIdSchema = createOtelHexIdSchema(SPAN_ID_BYTES);
var telemetryMetricIdSchema = createOtelHexIdSchema(METRIC_ID_BYTES);
var telemetryLogSchema = z3.object({
id: telemetryLogIdSchema,
scope: z3.string(),
resource: telemetryResourceSchema,
attributes: telemetryAttributeSchema.optional(),
level: z3.enum(["debug", "info", "warn", "error", "fatal"]),
/**
* The raw message with ANSI escape codes.
* Useful for displaying in the terminal.
* e.g., will preserve style of `chalk.bold.red("Hello")`
*/
message: z3.string(),
/**
* The message without any ANSI escape codes.
* Useful for using messages outside of the terminal.
*/
messageUnstyled: z3.string(),
timestamp: z3.bigint(),
stack: z3.string(),
traceId: telemetryTraceIdSchema.optional(),
spanId: telemetrySpanIdSchema.optional(),
error: z3.custom().optional()
});
var telemetrySpanSchema = z3.object({
id: telemetrySpanIdSchema,
scope: z3.string(),
resource: telemetryResourceSchema,
attributes: telemetryAttributeSchema.optional(),
name: z3.string(),
/**
* The timestamp when the span started in nanoseconds.
*/
startTimestamp: z3.bigint(),
/**
* The timestamp when the span ended in nanoseconds.
* Is undefined if the span hasn't ended yet.
*/
endTimestamp: z3.bigint(),
/**
* The duration of the span in nanoseconds.
* Is undefined if the span hasn't ended yet.
*/
duration: z3.bigint(),
traceId: telemetryTraceIdSchema,
parentSpanId: z3.string().optional(),
logs: z3.array(
telemetryLogSchema.omit({ resource: true, scope: true, traceId: true, spanId: true })
)
});
var telemetryMetricSchema = z3.object({
id: telemetryMetricIdSchema,
scope: z3.string(),
resource: telemetryResourceSchema,
attributes: telemetryAttributeSchema.optional(),
kind: z3.enum(["counter", "updown", "histogram"]),
name: z3.string(),
value: z3.number().or(z3.bigint())
});
var telemetrySignalSchema = z3.discriminatedUnion("type", [
telemetryLogSchema.extend({ type: z3.literal("log") }),
telemetrySpanSchema.extend({ type: z3.literal("span") }),
telemetryMetricSchema.extend({ type: z3.literal("metric") })
]);
// telemetry/clients/base.ts
var defineScopes = /* @__PURE__ */ __name((scopes) => scopes, "defineScopes");
var TelemetryClient = class _TelemetryClient {
static {
__name(this, "TelemetryClient");
}
static #clients = [];
#scopesDefinition = {};
scope;
resource;
clientAttributes = {};
constructor(scopesDefinition, scope) {
this.scope = scope;
this.#scopesDefinition = scopesDefinition;
this.resource = this.getResource();
_TelemetryClient.#clients.push(this);
}
// Global consumers
static #globalConsumers = [];
/**
* Registers a callback consumer to receive telemetry data from all the clients.
* @param consumer - The consumer to register
* @returns A function that unregisters the consumer when called
* @example
* ```typescript
* const unregister = telemetry.registerGlobalConsumer(myConsumer);
* unregister(); // Later, to stop receiving events
* ```
*/
static registerGlobalConsumer(consumer) {
return registerConsumer(consumer, _TelemetryClient.#globalConsumers);
}
/**
* Flushes any globally pending telemetry data, ensuring that all the consumers
* of all the TelemetryClient instances have finished processing before returning
* or until the timeout is reached.
* @param timeoutMs - Maximum time to wait in milliseconds (default: 10000ms)
* @returns A promise that resolves when flushing is complete or timeout is reached
*/
static async flushAllConsumers(timeoutMs = 1e4) {
await Promise.all(_TelemetryClient.#clients.map((client) => client.flushConsumers(timeoutMs)));
}
// Local consumers
#consumers = [];
/**
* Registers a callback consumer to receive telemetry data from this client.
* @param consumer - The consumer to register
* @returns A function that unregisters the consumer when called
* @example
* ```typescript
* const unregister = telemetry.registerConsumer(myConsumer);
* unregister(); // Later, to stop receiving events
* ```
*/
registerConsumer(consumer) {
try {
return registerConsumer(consumer, this.#consumers);
} catch (error) {
this.log.error({
message: "Error registering telemetry consumer.",
error,
attributes: { consumer }
});
return () => void 0;
}
}
/**
* Flushes any pending telemetry data, ensuring that all consumers have finished
* processing before returning or until the timeout is reached.
* @param timeoutMs - Maximum time to wait in milliseconds (default: 10000ms)
* @returns A promise that resolves when flushing is complete or timeout is reached
*/
async flushConsumers(timeoutMs = 1e4) {
try {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
let allDone = true;
for (const { instance, queue } of this.#allConsumers) {
if (queue.length() > 0 || instance.isProcessing?.()) {
allDone = false;
break;
}
}
if (allDone) return;
await new Promise((resolve) => setTimeout(resolve, 50));
}
} catch (error) {
this.log.error({
message: "Error flushing telemetry consumers.",
error,
attributes: { timeoutMs }
});
}
}
/**
* Sets a client-level attribute that will be included in all telemetry data from this client.
* @param key - The attribute key
* @param value - The attribute value (must be serializable, else will be ignored)
* @example
* ```typescript
* telemetry.setAttribute("modelId", "abc");
* telemetry.setAttribute("region", "us-west-2");
* ```
*/
setAttribute(key, value) {
this.clientAttributes[key] = value;
}
/**
* Sets multiple client-level attributes that will be included in all telemetry data from this client.
* @param attributes - The attributes to set
* @example
* ```typescript
* telemetry.setAttributes({ modelId: "abc", region: "us-west-2" });
* ```
*/
setAttributes(attributes) {
this.clientAttributes = { ...this.clientAttributes, ...attributes };
}
/**
* Measure the duration and capture logs about an operation.
* Automatically handles both sync and async functions, preserving their return types.
* trace() calls can be nested and will produce nested spans.
* @param name - The name of the operation being traced
* @param fn - The function to execute within the span context (sync or async)
* @param options - Optional attributes and parent span
* @returns The result of the function (direct value for sync, Promise for async)
* @example
* ```typescript
* // Sync function - no await needed
* const hash = telemetry.trace("compute-hash", ({ log, setAttribute }) => {
* log.info({ message: "Computing hash" });
* const result = computeHash(data);
* setAttribute("algorithm", "sha256");
* return result;
* }, { attributes: { dataSize: data.length } });
*
* // Async function - await the result
* const user = await telemetry.trace("fetch-user", async ({ log }) => {
* log.info({ message: "Fetching user" });
* const response = await fetch(`/api/users/${id}`);
* return response.json();
* }, { attributes: { userId: id } });
*
* // Early end example:
* telemetry.trace("process-item", ({ end }) => {
* if (shouldSkip) {
* end();
* return;
* }
* // ... process item
* });
* ```
*/
trace(name, fn, options = {}) {
const parentSpan = options.parent ?? this.getCurrentSpan();
const parentSpanData = parentSpan?.getData();
const spanData = {
id: generateSpanId(),
resource: this.resource,
scope: this.scope,
attributes: { ...options.attributes, ...this.clientAttributes },
name,
startTimestamp: ns.now(),
endTimestamp: -1n,
duration: -1n,
traceId: parentSpanData?.traceId || generateTraceId(),
parentSpanId: parentSpanData?.id,
logs: []
};
const span = this.#createSpanHandle(spanData);
return this.runWithSpanData(spanData, () => {
try {
const result = fn(span);
if (result instanceof Promise) return result.finally(() => span.end());
span.end();
return result;
} catch (error) {
span.end();
throw error;
}
});
}
/**
* Get the ambient tracing span handle.
* @returns The current tracing span parent (if any)
*/
getCurrentSpan() {
const spanData = this.getCurrentSpanData();
if (!spanData) return;
return this.#createSpanHandle(spanData);
}
/**
* Send a telemetry signal to all consumers.
* This a raw method, prefer using log.*(), counter(), updown(), histogram(), etc.
* @param signal - The telemetry signal to send
*/
sendSignal(signal, throwOnError = false) {
try {
signal.attributes = { ...signal.attributes, ...this.clientAttributes };
const { error, data: parsedSignal } = telemetrySignalSchema.safeParse(signal);
if (error)
throw lifeError({
code: "Validation",
message: "Invalid telemetry signal shape. It has been ignored.",
attributes: { signal },
cause: error
});
const scopeDefinition = this.#scopesDefinition?.[parsedSignal?.scope ?? ""];
if (!scopeDefinition) {
throw lifeError({
code: "Validation",
message: `Invalid telemetry scope '${parsedSignal.scope}' in signal. It has been ignored.`,
attributes: { parsedSignal }
});
}
if (scopeDefinition.requiredAttributesSchema) {
const { error: attributesError, data: parsedAttributes } = scopeDefinition.requiredAttributesSchema.safeParse(parsedSignal?.attributes ?? {});
if (attributesError)
throw lifeError({
code: "Validation",
message: `Signal contains invalid required attributes for scope '${parsedSignal.scope}'. It has been ignored.`,
attributes: { parsedSignal },
cause: attributesError
});
parsedSignal.attributes = { ...parsedSignal.attributes, ...parsedAttributes };
}
const [errSerialize, serializedSignal] = canon.stringify(parsedSignal);
if (errSerialize) {
throw lifeError({
code: "Validation",
message: "Failed to serialize telemetry signal. It has been ignored.",
attributes: { parsedSignal },
cause: errSerialize
});
}
const MAX_SIGNAL_SIZE = 1024 * 1024;
if (serializedSignal.length > MAX_SIGNAL_SIZE) {
throw lifeError({
code: "Validation",
message: "Telemetry signal is too large. It has been ignored.",
attributes: { parsedSignal }
});
}
for (const consumer of this.#allConsumers) consumer.queue.push(parsedSignal);
} catch (error) {
if (throwOnError) throw error;
else
this.log.error({
message: "Unexpected error sending telemetry signal. It has been ignored.",
error,
attributes: { signal }
});
}
}
/**
* Unsafe version of sendSignal() bypassing all validation and checks.
* Used internally to forward telemetry signals between processes.
* @internal
*/
_unsafeSendSignal(signal) {
for (const consumer of this.#allConsumers) consumer.queue.push(signal);
}
/**
* Creates a counter metric for tracking monotonically increasing values.
* @param name - The name of the counter metric
* @returns An object with methods to increment the counter
* @example
* ```typescript
* const requestCounter = telemetry.counter("http_requests_total");
* requestCounter.increment({ method: "GET", status: "200" });
* requestCounter.add(5, { batch: "true" });
* ```
*/
counter(name) {
return {
add: /* @__PURE__ */ __name((n, attributes) => {
try {
const fullMetric = {
id: generateMetricId(),
resource: this.resource,
scope: this.scope,
attributes,
name,
kind: "counter",
value: n
};
this.sendSignal({ type: "metric", ...fullMetric });
} catch (error) {
this.log.error({
message: "Error adding to counter metric.",
error,
attributes: { name, n, attributes }
});
}
}, "add"),
increment: /* @__PURE__ */ __name((attributes) => {
try {
this.counter(name).add(1, attributes);
} catch (error) {
this.log.error({
message: "Error incrementing counter metric.",
error,
attributes: { name, attributes }
});
}
}, "increment")
};
}
/**
* Creates an up/down counter metric for tracking values that can increase or decrease.
* @param name - The name of the up/down counter metric
* @returns An object with methods to modify the counter
* @example
* ```typescript
* const connectionGauge = telemetry.updown("active_connections");
* connectionGauge.increment(); // New connection
* connectionGauge.decrement(); // Connection closed
* connectionGauge.add(10); // Bulk connections
* ```
*/
updown(name) {
return {
add: /* @__PURE__ */ __name((n, attributes) => {
try {
const fullMetric = {
id: generateMetricId(),
resource: this.resource,
scope: this.scope,
attributes,
name,
kind: "updown",
value: n
};
this.sendSignal({ type: "metric", ...fullMetric });
} catch (error) {
this.log.error({
message: "Error adding to updown metric.",
error,
attributes: { name, n, attributes }
});
}
}, "add"),
remove: /* @__PURE__ */ __name((n, attributes) => {
try {
this.updown(name).add(-n, attributes);
} catch (error) {
this.log.error({
message: "Error removing from updown metric.",
error,
attributes: { name, n, attributes }
});
}
}, "remove"),
increment: /* @__PURE__ */ __name((attributes) => {
try {
this.updown(name).add(1, attributes);
} catch (error) {
this.log.error({
message: "Error incrementing updown metric.",
error,
attributes: { name, attributes }
});
}
}, "increment"),
decrement: /* @__PURE__ */ __name((attributes) => {
try {
this.updown(name).add(-1, attributes);
} catch (error) {
this.log.error({
message: "Error decrementing updown metric.",
error,
attributes: { name, attributes }
});
}
}, "decrement")
};
}
/**
* Creates a histogram metric for recording value distributions over time.
* @param name - The name of the histogram metric
* @returns An object with a method to record values
* @example
* ```typescript
* const latencyHistogram = telemetry.histogram("request_duration_ms");
* latencyHistogram.record(responseTime, { endpoint: "/api/users" });
* ```
*/
histogram(name) {
return {
record: /* @__PURE__ */ __name((value, attributes) => {
try {
const fullMetric = {
id: generateMetricId(),
resource: this.resource,
scope: this.scope,
attributes,
name,
kind: "histogram",
value
};
this.sendSignal({ type: "metric", ...fullMetric });
} catch (error) {
this.log.error({
message: "Error recording histogram metric.",
error,
attributes: { name, value, attributes }
});
}
}, "record")
};
}
/**
* Log writer for recording events at different severity levels.
* Logs are automatically associated with the current span context if one exists.
* @example
* ```typescript
* telemetry.log.info({ message: "Server started", attributes: { port: 3000 } });
* telemetry.log.error({ error: new Error("Connection failed"), attributes: { host: "db.example.com" } });
* telemetry.log.warn({ message: "Deprecated API used", attributes: { endpoint: "/v1/users" } });
* ```
*/
log = {
debug: /* @__PURE__ */ __name((input) => this.#emitLog("debug", input), "debug"),
info: /* @__PURE__ */ __name((input) => this.#emitLog("info", input), "info"),
warn: /* @__PURE__ */ __name((input) => this.#emitLog("warn", input), "warn"),
error: /* @__PURE__ */ __name((input) => this.#emitLog("error", input), "error"),
fatal: /* @__PURE__ */ __name((input) => this.#emitLog("fatal", input), "fatal")
};
// ========== Private Methods ==========
/**
* Get all consumers.
* Including local and global consumers.
*/
get #allConsumers() {
return this.#consumers.concat(_TelemetryClient.#globalConsumers);
}
#endSpan(spanData) {
try {
if (spanData.endTimestamp !== -1n) return;
spanData.endTimestamp = ns.now();
spanData.duration = ns.since(spanData.startTimestamp);
this.sendSignal({ type: "span", ...spanData });
} catch (error) {
this.log.error({
message: "Error ending span.",
error,
attributes: { span: spanData }
});
}
}
/**
* Private helper to create a span and its associated methods.
* Used by both trace() and traceBlock() to avoid code duplication.
*/
#createSpanHandle(spanData) {
const getData = /* @__PURE__ */ __name(() => deepClone(spanData), "getData");
const _getWritableData = /* @__PURE__ */ __name(() => spanData, "_getWritableData");
const end = /* @__PURE__ */ __name(() => this.#endSpan(spanData), "end");
const setAttribute = /* @__PURE__ */ __name((key, value) => {
if (spanData.endTimestamp !== -1n) {
this.log.error({
message: "Attempted to call 'setAttribute()' on already ended span. This is unexpected and must be fixed.",
attributes: { span: spanData, key, value }
});
return;
}
spanData.attributes = spanData.attributes || {};
spanData.attributes[key] = value;
}, "setAttribute");
const setAttributes = /* @__PURE__ */ __name((_attributes) => {
if (spanData.endTimestamp !== -1n) {
this.log.error({
message: "Attempted to call 'setAttributes()' on already ended span. This is unexpected and must be fixed.",
attributes: { span: spanData, attributes: _attributes }
});
return;
}
spanData.attributes = { ...spanData.attributes, ..._attributes };
}, "setAttributes");
const span = { end, setAttribute, setAttributes, getData, _getWritableData };
const createSpanLogMethod = /* @__PURE__ */ __name((level) => (input) => {
if (spanData.endTimestamp !== -1n) {
this.log.error({
message: `Attempted to call 'log.${level}()' on already ended span. This is unexpected and must be fixed.`,
attributes: { span: spanData, input }
});
return;
}
this.#emitLog(level, { ...input, span });
}, "createSpanLogMethod");
const log = {
debug: createSpanLogMethod("debug"),
info: createSpanLogMethod("info"),
warn: createSpanLogMethod("warn"),
error: createSpanLogMethod("error"),
fatal: createSpanLogMethod("fatal")
};
Object.assign(span, { log });
return span;
}
/**
* Internal log method that handles all log levels
*/
#emitLog(level, input, fromEmitLog = false) {
try {
if (fromEmitLog) {
let consoleMethod = "log";
if (level === "error") consoleMethod = "error";
else if (level === "warn") consoleMethod = "warn";
console[consoleMethod](`[${level}] ${JSON.stringify(input)}`);
return;
}
const spanData = input.span?._getWritableData() ?? this.getCurrentSpanData();
const error = input.error;
const message = "message" in input ? input.message : error?.message;
if (!message)
throw lifeError({
code: "Validation",
message: "No message provided in log input or error. This is unexpected and must be fixed.",
attributes: { level, input }
});
const log = {
id: generateLogId(),
resource: this.resource,
scope: this.scope,
attributes: input.attributes,
level,
message,
messageUnstyled: stripAnsi(message),
timestamp: ns.now(),
stack: error?.stack ?? new Error(".").stack ?? "unknown",
traceId: spanData?.traceId,
spanId: spanData?.id,
error
};
if (input.span && spanData) spanData.logs.push(log);
this.sendSignal({ type: "log", ...log }, true);
} catch (error) {
if (this.resource.environment === "test") throw error;
if (fromEmitLog) {
console.error("[emitLog] Error occurred while logging error:", error);
return;
}
this.#emitLog(
"error",
{
message: "Error emitting log.",
error,
attributes: { level, input }
},
true
);
}
}
};
// package.json
var package_default = {
name: "life",
version: "0.40.0",
description: "Life.js is the first fullstack framework to build agentic web applications. It is minimal, extensible, and typesafe. Well, everything you love.",
author: "Lila Rest",
license: "MIT+Services",
homepage: "https://lifejs.org",
bugs: {
url: "https://github.com/lifejs/lifejs/issues"
},
repository: {
type: "git",
url: "https://github.com/lifejs/lifejs"
},
files: [
"dist/",
"package.json",
"README.md",
"LICENSE",
"LICENSE.SERVICES"
],
keywords: [
"react",
"framework",
"typescript",
"fullstack",
"agents",
"agentic"
],
scripts: {
build: "tsup",
dev: "tsup --watch",
types: "tsc --noEmit --emitDeclarationOnly false",
test: "dotenv -e ../../.env -- vitest run",
prepack: "cp ../../README.md README.md && cp ../../LICENSE LICENSE && cp ../../LICENSE.SERVICES LICENSE.SERVICES"
},
bin: {
life: "./dist/cli/index.mjs"
},
sideEffects: false,
engines: {
node: ">=20.0.0"
},
publishConfig: {
access: "public"
},
exports: {
"./config": {
import: {
types: "./dist/exports/config.d.ts",
default: "./dist/exports/config.mjs"
},
require: {
types: "./dist/exports/config.d.ts",
default: "./dist/exports/config.js"
}
},
"./server": {
import: {
types: "./dist/exports/server.d.ts",
default: "./dist/exports/server.mjs"
},
require: {
types: "./dist/exports/server.d.ts",
default: "./dist/exports/server.js"
}
},
"./client": {
import: {
types: "./dist/exports/client.d.ts",
default: "./dist/exports/client.mjs"
},
require: {
types: "./dist/exports/client.d.ts",
default: "./dist/exports/client.js"
}
},
"./react": {
import: {
types: "./dist/exports/react.d.ts",
default: "./dist/exports/react.mjs"
},
require: {
types: "./dist/exports/react.d.ts",
default: "./dist/exports/react.js"
}
}
},
dependencies: {
"@ast-grep/napi": "^0.39.9",
"@hono/node-server": "^1.19.6",
"@hono/node-ws": "^1.2.0",
"@huggingface/transformers": "^3.7.6",
"@inkjs/ui": "^2.0.0",
"@jridgewell/trace-mapping": "^0.3.31",
"@livekit/rtc-node": "^0.13.20",
"@nanostores/react": "^1.0.0",
"@paralleldrive/cuid2": "^3.0.4",
"@vitest/browser-playwright": "^4.0.8",
"@zenobius/ink-mouse": "^1.0.3",
birpc: "^2.8.0",
chalk: "^5.6.2",
chokidar: "^4.0.3",
commander: "^14.0.2",
dotenv: "^17.2.3",
"error-stack-parser": "^2.1.4",
esbuild: "^0.27.0",
"fast-npm-meta": "^0.4.7",
figures: "^6.1.0",
globby: "^15.0.0",
hono: "^4.10.5",
imurmurhash: "^0.1.4",
ink: "^6.5.0",
"ink-link": "^5.0.0",
"livekit-client": "^2.15.15",
"livekit-server-sdk": "^2.14.0",
"mathjax-full": "^3.2.2",
"mdast-flatten-nested-lists": "^1.2.0",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-gfm": "^3.1.0",
"mdast-util-math": "^3.0.0",
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-to-markdown": "^2.1.2",
"micromark-extension-gfm": "^3.0.0",
"micromark-extension-math": "^3.1.0",
"micromark-extension-mdx-jsx": "^3.0.2",
nanostores: "^1.0.1",
"onnxruntime-node": ">=1.24.0-0",
"oxc-parser": "^0.97.0",
"oxc-walker": "^0.5.2",
react: "^19.2.0",
resolve: "^1.22.11",
"serialize-error": "^12.0.0",
"speech-rule-engine": "^5.0.0-alpha.8",
"string-width": "^8.1.0",
superjson: "^2.2.5",
"to-words": "^4.8.0",
"ua-parser-js": "^2.0.5",
"wrap-ansi": "^9.0.2",
yjs: "^13.6.27",
zod: "^4.1.12"
},
devDependencies: {
"@cartesia/cartesia-js": "^2.2.9",
"@deepgram/sdk": "4.11.2",
"@mistralai/mistralai": "^1.10.0",
"@playwright/test": "^1.56.1",
"@types/imurmurhash": "^0.1.4",
"@types/mdast": "^4.0.4",
"@types/node": "^24.10.1",
"@types/react": "^19.2",
"@types/react-dom": "^19.2",
"@types/resolve": "^1.20.6",
"@types/ws": "^8.18.1",
"@vitest/browser": "^4.0.8",
"dotenv-cli": "^11.0.0",
"esbuild-plugin-preserve-directives": "^0.0.11",
openai: "^5.20.2",
playwright: "^1.56.1",
tsup: "^8.5.0",
"type-fest": "^5.2.0",
vitest: "^4.0.8"
},
peerDependencies: {
"@cartesia/cartesia-js": "^2.2.5",
"@deepgram/sdk": "4.9.1",
"@mistralai/mistralai": "^1.7.4",
openai: "^5.9.0"
},
peerDependenciesMeta: {
"@cartesia/cartesia-js": {
optional: true
},
"@deepgram/sdk": {
optional: true
},
"@mistralai/mistralai": {
optional: true
},
openai: {
optional: true
}
}
};
export {
canon,
ns,
stripAnsi,
telemetrySignalSchema,
defineScopes,
TelemetryClient,
package_default
};
//# sourceMappingURL=chunk-6CBODJTF.mjs.map