UNPKG

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
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