@mastra/core
Version:
1,381 lines • 273 kB
JavaScript
import { i as __toESM, t as __commonJSMin } from "./rolldown-runtime-DP3BCW9_.js";
import { t as MastraBase } from "./base-BeUQ6mLP.js";
import { i as MastraError, n as ErrorDomain, t as ErrorCategory } from "./error-MjDSls8S.js";
import { Kt as listScoresArgsSchema, Ut as listMetricsArgsSchema, Vt as listLogsArgsSchema, f as EntityType, zt as listFeedbackArgsSchema } from "./utils-DxsDNzD2.js";
import { n as MessageList } from "./message-list-mC29laJJ.js";
import { $ as TABLE_SCORERS, Gt as listBranchesArgsSchema, Jt as listTracesArgsSchema, Mt as getBranchArgsSchema, Q as TABLE_SCHEMAS, bt as BRANCH_SPAN_TYPE_SET, jt as extractBranchSpans, nn as toTraceSpan, rn as toTraceSpans } from "./constants-BfpAlX25.js";
import { c as MastraCompositeStore, d as normalizePerPage, f as InMemoryThreadStateStorage, u as calculatePagination } from "./filesystem-versioned-M7zmYTpZ.js";
import { t as StorageDomain } from "./base-Bt5IshKX.js";
import { coreFeatures } from "./features/index.js";
import { i as InMemoryAgentsStorage, n as FilesystemAgentsStorage, r as InMemoryDB } from "./source-xpSE-8BU.js";
import { t as InMemoryFavoritesStorage } from "./inmemory-BRKxS_86.js";
import { n as InMemoryMCPClientsStorage, t as FilesystemMCPClientsStorage } from "./filesystem-CW80Gvlh.js";
import { n as InMemoryMCPServersStorage, t as FilesystemMCPServersStorage } from "./filesystem-BAFBDAG1.js";
import { t as InMemoryNotificationsStorage } from "./storage-B1u4gxRl.js";
import { n as InMemoryPromptBlocksStorage, t as FilesystemPromptBlocksStorage } from "./filesystem-hzUov7lW.js";
import { n as InMemoryScorerDefinitionsStorage, t as FilesystemScorerDefinitionsStorage } from "./filesystem-CffraaeW.js";
import { n as InMemorySkillsStorage, t as FilesystemSkillsStorage } from "./filesystem-DDQhIkK3.js";
import { n as InMemoryWorkspacesStorage, t as FilesystemWorkspacesStorage } from "./filesystem-ePb2Dod3.js";
import { randomUUID } from "crypto";
import { z } from "zod/v4";
import { jsonSchemaToZod } from "@mastra/schema-compat/json-to-zod";
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
import { dirname, extname, join, relative, resolve, sep } from "path";
//#region src/storage/factory-storage.ts
/**
* Thrown by `insertOne`/`upsertOne` when a unique constraint rejects the row.
* Backends map their native duplicate-key errors onto this type so domains
* can implement insert-or-recover races portably.
*/
var UniqueViolationError = class extends Error {
collection;
constructor(collection, options) {
super(`Unique constraint violation on collection '${collection}'`, options);
this.name = "UniqueViolationError";
this.collection = collection;
}
};
/**
* Base class for application domains owned by a {@link FactoryStorage}.
* Domains are bound once when registered and share their owner's connection.
*/
var FactoryStorageDomain = class extends StorageDomain {
name;
#storage;
constructor(name) {
if (!name.trim()) throw new Error("Factory storage domain name must not be empty");
super({
component: "STORAGE",
name
});
this.name = name;
}
/** @internal Bound by {@link FactoryStorage.registerDomain}. */
__bindFactoryStorage(storage) {
if (this.#storage && this.#storage !== storage) throw new Error(`Factory storage domain '${this.name}' is already bound to another storage instance`);
this.#storage = storage;
}
get storage() {
if (!this.#storage) throw new Error(`Factory storage domain '${this.name}' has not been registered`);
return this.#storage;
}
/**
* Initialize this domain (via its owning storage) if it hasn't been yet.
* Lets consumers holding a domain handle run the same fail-soft readiness
* check as {@link FactoryStorage.ensureDomainReady} without also needing a
* reference to the storage backend.
*/
ensureReady() {
return this.storage.ensureDomainReady(this.name);
}
get ops() {
return this.storage.ops;
}
ensureCollections(schemas) {
return this.storage.ensureCollections(schemas);
}
};
/**
* A pluggable application-storage backend: one database powering agent state
* (via {@link getMastraStorage}) and app-owned collections (via {@link ops}).
*/
var FactoryStorage = class {
#domains = /* @__PURE__ */ new Map();
#readyDomains = /* @__PURE__ */ new Set();
#domainErrors = /* @__PURE__ */ new Map();
#domainInitPromises = /* @__PURE__ */ new Map();
#storageReady = false;
#storageInitPromise;
/** Open/validate the backend, then initialize registered domains fail-soft. */
async init() {
await this.#ensureStorageReady();
await Promise.all([...this.#domains.keys()].map((name) => this.#initDomain(name).catch(() => void 0)));
}
registerDomain(domain) {
if (this.#domains.has(domain.name)) throw new Error(`Factory storage domain '${domain.name}' is already registered`);
domain.__bindFactoryStorage(this);
this.#domains.set(domain.name, domain);
return domain;
}
getDomain(name) {
const domain = this.#domains.get(name);
if (!domain) throw new Error(`Factory storage domain '${name}' is not registered`);
return domain;
}
hasDomain(name) {
return this.#domains.has(name);
}
domainNames() {
return [...this.#domains.keys()];
}
isDomainReady(name) {
return this.#readyDomains.has(name);
}
domainInitError(name) {
return this.#domainErrors.get(name);
}
async ensureDomainReady(name) {
this.getDomain(name);
await this.#ensureStorageReady();
await this.#initDomain(name);
}
async #ensureStorageReady() {
if (this.#storageReady) return;
if (this.#storageInitPromise) return this.#storageInitPromise;
const initPromise = (async () => {
await this.initStorage();
this.#storageReady = true;
})();
this.#storageInitPromise = initPromise;
try {
await initPromise;
} finally {
if (this.#storageInitPromise === initPromise) this.#storageInitPromise = void 0;
}
}
#initDomain(name) {
if (this.#readyDomains.has(name)) return Promise.resolve();
const pending = this.#domainInitPromises.get(name);
if (pending) return pending;
const domain = this.getDomain(name);
this.#domainErrors.delete(name);
const initPromise = (async () => {
try {
await domain.init();
this.#readyDomains.add(name);
} catch (error) {
this.#domainErrors.set(name, error);
throw error;
} finally {
this.#domainInitPromises.delete(name);
}
})();
this.#domainInitPromises.set(name, initPromise);
return initPromise;
}
};
//#endregion
//#region src/storage/domains/observability/base.ts
/**
* Base storage class for observability data (traces, metrics, logs, scores, feedback).
* Not abstract -- provides default implementations that throw "not implemented" errors.
* Storage adapters override only the methods they support.
*/
var ObservabilityStorage = class ObservabilityStorage extends StorageDomain {
constructor() {
super({
component: "STORAGE",
name: "OBSERVABILITY"
});
}
async dangerouslyClearAll() {}
/**
* Provides hints for tracing strategy selection by the MastraStorageExporter.
* Storage adapters can override this to specify their preferred and supported strategies.
*/
get observabilityStrategy() {
return {
preferred: "batch-with-updates",
supported: [
"realtime",
"batch-with-updates",
"insert-only"
]
};
}
/**
* Provides hints for tracing strategy selection by the MastraStorageExporter.
* Storage adapters can override this to specify their preferred and supported strategies.
* @deprecated Use {@link observabilityStrategy} instead.
* @see {@link observabilityStrategy} for the replacement property.
*/
get tracingStrategy() {
return this.observabilityStrategy;
}
/**
* Reports the tracing strategy currently in effect for this attached observability store.
*
* Single-strategy stores can rely on the default implementation. Multi-strategy stores
* should override this getter only when they can determine the actual configured mode
* from storage-owned configuration, not exporter state.
*/
get runtimeTracingStrategy() {
const supportedStrategies = this.observabilityStrategy.supported;
return supportedStrategies.length === 1 ? supportedStrategies[0] : void 0;
}
/**
* Optional feature list for observability storage APIs.
* Stores should override this to opt in to the APIs they support explicitly.
* Older stores and older package versions will simply omit it, which keeps page mode working.
*/
getFeatures() {}
/**
* Creates a single Span record in the storage provider.
*/
async createSpan(_args) {
throw new MastraError({
id: "OBSERVABILITY_CREATE_SPAN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support creating spans"
});
}
/**
* Updates a single Span with partial data. Primarily used for realtime trace creation.
*
* @deprecated This method only works with stores that support span updates,
* It will be removed in the future. Instead try to add all data to a span before
* ending it.
*/
async updateSpan(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_UPDATE_SPAN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support updating spans"
});
}
/**
* Retrieves a single span.
*/
async getSpan(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SPAN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting spans"
});
}
/**
* Retrieves a single root span.
*/
async getRootSpan(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_ROOT_SPAN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting root spans"
});
}
/**
* Retrieves a single trace with all its associated spans.
*/
async getTrace(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_TRACE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting traces"
});
}
/**
* Retrieves the structural skeleton of a trace -- parent/child links, span
* type, timing, and status -- with heavy fields (input, output, attributes,
* metadata, tags, links) excluded. Intended for waterfall/timeline rendering
* where the full payload would be wasteful.
*
* Default implementation forwards to {@link getTraceLight} (the legacy
* override surface). Backends should override either method -- the response
* shape is identical, and the unimplemented one delegates to the
* implemented one. The cycle guard is what makes that safe.
*/
async getStructure(args) {
if (this.getTraceLight === ObservabilityStorage.prototype.getTraceLight) throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_STRUCTURE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting trace structure"
});
return this.getTraceLight(args);
}
/**
* @deprecated Use {@link getStructure} instead. Default implementation
* forwards to {@link getStructure} so backends that only override the
* canonical name still work for legacy callers.
*/
async getTraceLight(args) {
if (this.getStructure === ObservabilityStorage.prototype.getStructure) throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_TRACE_LIGHT_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting lightweight traces"
});
return this.getStructure(args);
}
/**
* Retrieves the subtree of spans rooted at a given span, optionally bounded
* to `depth` levels of descendants.
*
* Default implementation prefers a two-step path: fetch the lightweight
* structure to determine which spans belong to the branch, then batch-fetch
* only those with full data. This avoids pulling the entire trace when the
* branch is a small slice of a large trace. Backends that don't yet
* implement {@link getStructure} or {@link getSpans} fall back to fetching
* the full trace and walking it in memory.
*/
async getBranch(args) {
const parsed = getBranchArgsSchema.parse(args);
try {
const skeleton = await this.getStructure({ traceId: parsed.traceId });
if (!skeleton) return null;
const branchSpanIds = extractBranchSpans(skeleton.spans, parsed.spanId, parsed.depth).map((s) => s.spanId);
if (branchSpanIds.length === 0) return null;
const { spans } = await this.getSpans({
traceId: parsed.traceId,
spanIds: branchSpanIds
});
if (spans.length === 0) return null;
spans.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
return {
traceId: parsed.traceId,
spans
};
} catch (error) {
if (!(error instanceof MastraError && (error.id === "OBSERVABILITY_STORAGE_GET_STRUCTURE_NOT_IMPLEMENTED" || error.id === "OBSERVABILITY_STORAGE_GET_TRACE_LIGHT_NOT_IMPLEMENTED" || error.id === "OBSERVABILITY_STORAGE_GET_SPANS_NOT_IMPLEMENTED"))) throw error;
}
const trace = await this.getTrace({ traceId: parsed.traceId });
if (!trace) return null;
const spans = extractBranchSpans(trace.spans, parsed.spanId, parsed.depth);
if (spans.length === 0) return null;
return {
traceId: parsed.traceId,
spans
};
}
/**
* Batch-fetches spans by spanId within a single trace. Used by the
* optimized {@link getBranch} path to fetch only the spans that belong to
* the requested branch (after walking the lightweight structure to identify
* them) instead of pulling the entire trace.
*/
async getSpans(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SPANS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch-fetching spans"
});
}
/**
* Retrieves a list of traces with optional filtering.
*/
async listTraces(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_TRACES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing traces"
});
}
/**
* Retrieves a lightweight list of traces with optional filtering.
*/
async listTracesLight(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_TRACES_LIGHT_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing lightweight traces"
});
}
/**
* Lists trace branches across all traces. Unlike {@link listTraces} (which
* returns one row per root-rooted trace), each row here is a single branch
* anchor span, including ones nested under a different root entity -- useful
* for "show me every run of agent X" regardless of caller. Pairs with
* {@link getBranch} to expand a single branch into its subtree.
*/
async listBranches(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_BRANCHES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing trace branches"
});
}
/**
* Creates multiple Spans in a single batch.
*/
async batchCreateSpans(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_CREATE_SPAN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch creating spans"
});
}
/**
* Updates multiple Spans in a single batch.
*/
async batchUpdateSpans(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_UPDATE_SPANS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch updating spans"
});
}
/**
* Deletes multiple traces and all their associated spans in a single batch operation.
*/
async batchDeleteTraces(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_DELETE_TRACES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch deleting traces"
});
}
/**
* Creates multiple log records in a single batch.
*/
async batchCreateLogs(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_CREATE_LOGS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch creating logs"
});
}
/**
* Retrieves a list of logs with optional filtering.
*/
async listLogs(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_LOGS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing logs"
});
}
/**
* Creates multiple metric observations in a single batch.
*/
async batchCreateMetrics(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_CREATE_METRICS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch creating metrics"
});
}
async listMetrics(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_METRICS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing metrics"
});
}
async getMetricAggregate(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_AGGREGATE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric aggregation"
});
}
async getMetricBreakdown(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_BREAKDOWN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric breakdown"
});
}
async getMetricTimeSeries(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_TIME_SERIES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric time series"
});
}
async getMetricPercentiles(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_PERCENTILES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric percentiles"
});
}
async getMetricNames(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_NAMES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric name discovery"
});
}
async getMetricLabelKeys(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_METRIC_LABEL_KEYS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support metric label key discovery"
});
}
async getMetricLabelValues(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_LABEL_VALUES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support label value discovery"
});
}
async getEntityTypes(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_ENTITY_TYPES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support entity type discovery"
});
}
async getEntityNames(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_ENTITY_NAMES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support entity name discovery"
});
}
async getServiceNames(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SERVICE_NAMES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support service name discovery"
});
}
async getEnvironments(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_ENVIRONMENTS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support environment discovery"
});
}
async getTags(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_TAGS_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support tag discovery"
});
}
/**
* Creates a single score record.
*/
async createScore(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_CREATE_SCORE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support creating scores"
});
}
/**
* Creates multiple score observations in a single batch.
*/
async batchCreateScores(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_CREATE_SCORES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch creating scores"
});
}
/**
* Retrieves a list of scores with optional filtering.
*/
async listScores(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_SCORES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing scores"
});
}
/**
* Retrieves a single score by its score ID.
*/
async getScoreById(_scoreId) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SCORE_BY_ID_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support getting scores by ID"
});
}
async getScoreAggregate(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SCORE_AGGREGATE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support score aggregation"
});
}
async getScoreBreakdown(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SCORE_BREAKDOWN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support score breakdown"
});
}
async getScoreTimeSeries(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SCORE_TIME_SERIES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support score time series"
});
}
async getScorePercentiles(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_SCORE_PERCENTILES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support score percentiles"
});
}
/**
* Creates a single feedback record.
*/
async createFeedback(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_CREATE_FEEDBACK_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support creating feedback"
});
}
/**
* Creates multiple feedback observations in a single batch.
*/
async batchCreateFeedback(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_BATCH_CREATE_FEEDBACK_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support batch creating feedback"
});
}
/**
* Retrieves a list of feedback with optional filtering.
*/
async listFeedback(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_LIST_FEEDBACK_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support listing feedback"
});
}
async getFeedbackAggregate(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_FEEDBACK_AGGREGATE_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support feedback aggregation"
});
}
async getFeedbackBreakdown(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_FEEDBACK_BREAKDOWN_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support feedback breakdown"
});
}
async getFeedbackTimeSeries(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_FEEDBACK_TIME_SERIES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support feedback time series"
});
}
async getFeedbackPercentiles(_args) {
throw new MastraError({
id: "OBSERVABILITY_STORAGE_GET_FEEDBACK_PERCENTILES_NOT_IMPLEMENTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support feedback percentiles"
});
}
};
//#endregion
//#region src/storage/utils.ts
function hasErrorCode(error, codes) {
const seen = /* @__PURE__ */ new Set();
let current = error;
while (current && typeof current === "object" && !seen.has(current)) {
seen.add(current);
if ("code" in current && codes.has(current.code)) return true;
current = "cause" in current ? current.cause : void 0;
}
return false;
}
const DURATION_UNIT_MS = {
ms: 1,
s: 1e3,
m: 60 * 1e3,
h: 3600 * 1e3,
d: 1440 * 60 * 1e3,
w: 10080 * 60 * 1e3
};
/**
* Parses a retention {@link Duration} into milliseconds.
*
* Accepts a raw number of milliseconds or a `<number><unit>` string where unit
* is one of `ms`, `s`, `m`, `h`, `d`, `w`.
*
* @throws Error if the input is not a valid duration.
*/
function parseDuration(duration) {
if (typeof duration === "number") {
if (!Number.isFinite(duration) || duration < 0) throw new Error(`Invalid retention duration: ${duration}. Must be a non-negative finite number of milliseconds.`);
return duration;
}
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/.exec(duration);
if (!match) throw new Error(`Invalid retention duration: "${duration}". Expected a number of milliseconds or a "<number><unit>" string (ms, s, m, h, d, w).`);
const value = Number(match[1]);
const unit = match[2];
return value * DURATION_UNIT_MS[unit];
}
function safelyParseJSON(input) {
if (input && typeof input === "object") return input;
if (input == null) return {};
if (typeof input === "string") try {
return JSON.parse(input);
} catch {
return input;
}
return {};
}
const SAFE_METADATA_KEY_PATTERN$1 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const MAX_METADATA_KEY_LENGTH$1 = 128;
const DISALLOWED_METADATA_KEYS$1 = /* @__PURE__ */ new Set([
"__proto__",
"prototype",
"constructor"
]);
function validateStorageMetadataFilter(metadata) {
if (metadata === void 0) return void 0;
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) throw new TypeError("Metadata filter must be an object.");
const entries = Object.entries(metadata);
for (const [key, value] of entries) {
if (key.length > MAX_METADATA_KEY_LENGTH$1 || !SAFE_METADATA_KEY_PATTERN$1.test(key) || DISALLOWED_METADATA_KEYS$1.has(key)) throw new TypeError(`Invalid metadata filter key "${key}".`);
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value))) throw new TypeError(`Invalid metadata filter value for key "${key}". Values must be string, finite number, boolean, or null.`);
}
return entries.length > 0 ? metadata : void 0;
}
function storageMessageMatchesMetadataFilter(content, filter) {
if (!filter) return true;
const parsedContent = typeof content === "string" ? safelyParseJSON(content) : content;
if (!parsedContent || typeof parsedContent !== "object" || Array.isArray(parsedContent)) return false;
const metadata = parsedContent.metadata;
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return false;
const metadataRecord = metadata;
return Object.entries(filter).every(([key, expected]) => Object.prototype.hasOwnProperty.call(metadataRecord, key) && metadataRecord[key] === expected);
}
/**
* Generic schema-driven row transformer.
* Uses TABLE_SCHEMAS to determine field types and apply appropriate transformations:
* - 'jsonb' fields: parsed from JSON strings using safelyParseJSON
* - 'timestamp' fields: optionally converted to Date objects
*
* @param row - The raw row from storage
* @param tableName - The table name to look up schema from TABLE_SCHEMAS
* @param options - Optional configuration for store-specific behavior
* @returns Transformed row with proper types
*/
function transformRow(row, tableName, options = {}) {
const { preferredTimestampFields = {}, convertTimestamps = false, nullValuePattern, fieldMappings = {} } = options;
const tableSchema = TABLE_SCHEMAS[tableName];
const result = {};
for (const [key, columnSchema] of Object.entries(tableSchema)) {
let value = row[fieldMappings[key] ?? key];
if (preferredTimestampFields[key]) value = row[preferredTimestampFields[key]] ?? value;
if (value === void 0 || value === null) continue;
if (nullValuePattern && value === nullValuePattern) continue;
if (columnSchema.type === "jsonb") if (typeof value === "string") result[key] = safelyParseJSON(value);
else if (typeof value === "object") result[key] = value;
else result[key] = value;
else if (columnSchema.type === "timestamp" && convertTimestamps && typeof value === "string") result[key] = new Date(value);
else result[key] = value;
}
return result;
}
/**
* Transform a raw score row from storage to ScoreRowData.
* Convenience wrapper around transformRow for the scores table (TABLE_SCORERS).
*
* @param row - The raw row from storage
* @param options - Optional configuration for store-specific behavior
* @returns Transformed ScoreRowData
*/
function transformScoreRow(row, options = {}) {
return transformRow(row, TABLE_SCORERS, options);
}
/**
* Converts a string to UPPER_SNAKE_CASE, preserving word boundaries from camelCase, PascalCase, kebab-case, etc.
*/
function toUpperSnakeCase(str) {
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
}
/**
* Generates a standardized error ID for storage and vector operations.
*
* Formats:
* - Storage: MASTRA_STORAGE_{STORE}_{OPERATION}_{STATUS}
* - Vector: MASTRA_VECTOR_{STORE}_{OPERATION}_{STATUS}
*
* This function auto-normalizes inputs to UPPER_SNAKE_CASE for flexibility.
* The store parameter is type-checked against canonical store names for IDE autocomplete.
*
* @param type - The operation type ('storage' or 'vector')
* @param store - The store adapter name (type-checked canonical names)
* @param operation - The operation that failed (e.g., 'LIST_THREADS_BY_RESOURCE_ID', 'QUERY')
* @param status - The status/error type (e.g., 'FAILED', 'INVALID_THREAD_ID', 'DUPLICATE_KEY')
*
* @example
* ```ts
* // Storage operations
* createStoreErrorId('storage', 'PG', 'LIST_THREADS', 'FAILED')
* // Returns: 'MASTRA_STORAGE_PG_LIST_THREADS_FAILED'
*
* // Vector operations
* createStoreErrorId('vector', 'CHROMA', 'QUERY', 'FAILED')
* // Returns: 'MASTRA_VECTOR_CHROMA_QUERY_FAILED'
*
* // Auto-normalizes any casing
* createStoreErrorId('storage', 'PG', 'listMessagesById', 'failed')
* // Returns: 'MASTRA_STORAGE_PG_LIST_MESSAGES_BY_ID_FAILED'
* ```
*/
function createStoreErrorId(type, store, operation, status) {
const normalizedStore = toUpperSnakeCase(store);
const normalizedOperation = toUpperSnakeCase(operation);
const normalizedStatus = toUpperSnakeCase(status);
return `MASTRA_${type === "storage" ? "STORAGE" : "VECTOR"}_${normalizedStore}_${normalizedOperation}_${normalizedStatus}`;
}
function createStorageErrorId(store, operation, status) {
return createStoreErrorId("storage", store, operation, status);
}
function createVectorErrorId(store, operation, status) {
return createStoreErrorId("vector", store, operation, status);
}
function getSqlType(type) {
switch (type) {
case "text": return "TEXT";
case "timestamp": return "TIMESTAMP";
case "float": return "FLOAT";
case "integer": return "INTEGER";
case "bigint": return "BIGINT";
case "jsonb": return "JSONB";
case "boolean": return "BOOLEAN";
default: return "TEXT";
}
}
function getDefaultValue(type) {
switch (type) {
case "text":
case "uuid": return "DEFAULT ''";
case "timestamp": return "DEFAULT '1970-01-01 00:00:00'";
case "integer":
case "bigint":
case "float": return "DEFAULT 0";
case "jsonb": return "DEFAULT '{}'";
case "boolean": return "DEFAULT FALSE";
default: return "DEFAULT ''";
}
}
function ensureDate(date) {
if (!date) return void 0;
return date instanceof Date ? date : new Date(date);
}
function serializeDate(date) {
if (!date) return void 0;
return ensureDate(date)?.toISOString();
}
/**
* Filter an array of items by date range. Used by in-memory storage adapters.
*
* This provides a consistent implementation of date range filtering with
* support for inclusive/exclusive bounds across all storage adapters.
*
* @param items - Array of items to filter
* @param getCreatedAt - Function to extract the createdAt date from an item
* @param dateRange - Optional date range filter configuration
* @returns Filtered array of items
*
* @example
* ```ts
* const filtered = filterByDateRange(
* messages,
* (msg) => new Date(msg.createdAt),
* { start: new Date('2024-01-01'), startExclusive: true }
* );
* ```
*/
function filterByDateRange(items, getCreatedAt, dateRange) {
if (!dateRange) return items;
let result = items;
if (dateRange.start) {
const startTime = ensureDate(dateRange.start).getTime();
result = result.filter((item) => {
const itemTime = getCreatedAt(item).getTime();
return dateRange.startExclusive ? itemTime > startTime : itemTime >= startTime;
});
}
if (dateRange.end) {
const endTime = ensureDate(dateRange.end).getTime();
result = result.filter((item) => {
const itemTime = getCreatedAt(item).getTime();
return dateRange.endExclusive ? itemTime < endTime : itemTime <= endTime;
});
}
return result;
}
/**
* Deep equality check for JSON values.
* Compares primitives, arrays, objects, and Date instances recursively.
*
* @param a - First value to compare
* @param b - Second value to compare
* @returns true if values are deeply equal, false otherwise
*/
function jsonValueEquals(a, b) {
if (a === void 0 || b === void 0) return a === b;
if (a === null || b === null) return a === b;
if (typeof a !== typeof b) return false;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (a instanceof Date || b instanceof Date) return false;
if (typeof a === "object") {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
return a.every((val, i) => jsonValueEquals(val, b[i]));
}
if (Array.isArray(a) || Array.isArray(b)) return false;
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) return false;
return aKeys.every((key) => jsonValueEquals(a[key], b[key]));
}
return a === b;
}
//#endregion
//#region src/storage/domains/observability/inmemory.ts
const OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
/** In-memory implementation of ObservabilityStorage for testing and development. */
var ObservabilityInMemory = class extends ObservabilityStorage {
db;
constructor({ db }) {
super();
this.db = db;
}
getFeatures() {
if (!this.deltaPollingFeatureEnabled()) return;
return ["delta-polling"];
}
async dangerouslyClearAll() {
this.db.traces.clear();
this.db.metricRecords.length = 0;
this.db.logRecords.length = 0;
this.db.scoreRecords.length = 0;
this.db.feedbackRecords.length = 0;
this.db.observabilityNextCursorId = 1;
this.db.traceCursorIds.clear();
this.db.branchCursorIds.clear();
this.db.metricCursorIds.clear();
this.db.logCursorIds.clear();
this.db.scoreCursorIds.clear();
this.db.feedbackCursorIds.clear();
}
deltaPollingFeatureEnabled() {
return coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
}
assertDeltaPollingEnabled() {
if (this.deltaPollingFeatureEnabled()) return;
throw new MastraError({
id: "OBSERVABILITY_DELTA_POLLING_NOT_SUPPORTED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "This storage provider does not support observability delta polling"
});
}
allocateObservabilityCursorId() {
const cursorId = this.db.observabilityNextCursorId;
this.db.observabilityNextCursorId += 1;
return cursorId;
}
/**
* Upserts a record into an append-only collection keyed by an id field.
*
* If an existing record with the same id is found, it is replaced in place
* (preserving its cursor id so delta polling does not re-emit it). Otherwise
* the record is appended and a fresh cursor id is allocated.
*/
upsertByIdField(records, cursorIds, record, idField) {
const id = record[idField];
if (id == null) throw new MastraError({
id: "OBSERVABILITY_MISSING_RECORD_ID",
domain: ErrorDomain.STORAGE,
category: ErrorCategory.USER,
text: `Observability record is missing required id field '${String(idField)}'`
});
const existingIndex = records.findIndex((existing) => existing[idField] === id);
if (existingIndex !== -1) {
const previous = records[existingIndex];
const cursorId = cursorIds.get(previous);
cursorIds.delete(previous);
records[existingIndex] = record;
if (cursorId !== void 0) cursorIds.set(record, cursorId);
return;
}
records.push(record);
cursorIds.set(record, this.allocateObservabilityCursorId());
}
encodeDeltaCursor(cursorId) {
return (cursorId ?? 0).toString();
}
decodeDeltaCursor(cursor) {
if (!/^\d+$/.test(cursor)) throw new MastraError({
id: "OBSERVABILITY_INVALID_DELTA_CURSOR",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.USER,
text: "Invalid observability delta cursor"
});
const cursorId = Number.parseInt(cursor, 10);
if (!Number.isInteger(cursorId) || cursorId < 0) throw new MastraError({
id: "OBSERVABILITY_INVALID_DELTA_CURSOR",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.USER,
text: "Invalid observability delta cursor"
});
return cursorId;
}
pageDeltaCursor(cursorId) {
if (!this.deltaPollingFeatureEnabled()) return {};
return { deltaCursor: this.encodeDeltaCursor(cursorId) };
}
maxMatchingCursorId(rows, cursorIds, matches) {
let maxCursorId = null;
for (const row of rows) {
const cursorId = cursorIds.get(row);
if (cursorId === void 0 || !matches(row)) continue;
if (maxCursorId === null || cursorId > maxCursorId) maxCursorId = cursorId;
}
return maxCursorId;
}
createBranchCursorKey(traceId, spanId) {
return `${traceId}\u0000${spanId}`;
}
maybeRegisterTraceCursor(traceEntry) {
const rootSpan = traceEntry.rootSpan;
if (!rootSpan) return;
if (!this.db.traceCursorIds.has(rootSpan.traceId)) this.db.traceCursorIds.set(rootSpan.traceId, this.allocateObservabilityCursorId());
}
maybeRegisterBranchCursor(span) {
if (!BRANCH_SPAN_TYPE_SET.has(span.spanType)) return;
const key = this.createBranchCursorKey(span.traceId, span.spanId);
if (!this.db.branchCursorIds.has(key)) this.db.branchCursorIds.set(key, this.allocateObservabilityCursorId());
}
buildDeltaResponse(rows, limit, fallbackCursorId) {
const visibleRows = rows.slice(0, limit);
const hasMore = rows.length > limit;
return {
rows: visibleRows.map((entry) => entry.row),
delta: {
limit,
hasMore
},
deltaCursor: visibleRows.length > 0 ? this.encodeDeltaCursor(visibleRows[visibleRows.length - 1].cursorId) : this.encodeDeltaCursor(fallbackCursorId)
};
}
listAppendOnlyDelta(rows, cursorIds, matches, after, limit) {
const currentCursorId = this.maxMatchingCursorId(rows, cursorIds, matches);
const streamCursorId = this.maxMatchingCursorId(rows, cursorIds, () => true);
const fallbackCursorId = currentCursorId ?? streamCursorId;
if (after === void 0) return {
rows: [],
delta: {
limit,
hasMore: false
},
deltaCursor: this.encodeDeltaCursor(fallbackCursorId)
};
const afterCursorId = this.decodeDeltaCursor(after);
const matchingRows = rows.flatMap((row) => {
const cursorId = cursorIds.get(row);
if (cursorId === void 0 || cursorId <= afterCursorId || !matches(row)) return [];
return [{
cursorId,
row
}];
}).sort((a, b) => a.cursorId - b.cursorId).slice(0, limit + 1);
return this.buildDeltaResponse(matchingRows, limit, fallbackCursorId);
}
getTraceCursorId(traceId, filters) {
const cursorId = this.db.traceCursorIds.get(traceId);
const traceEntry = this.db.traces.get(traceId);
if (cursorId === void 0 || !traceEntry?.rootSpan || !this.traceMatchesFilters(traceEntry, filters)) return null;
return cursorId;
}
getMaxTraceCursorId(filters) {
let maxCursorId = null;
for (const traceId of this.db.traceCursorIds.keys()) {
const cursorId = this.getTraceCursorId(traceId, filters);
if (cursorId === null) continue;
if (maxCursorId === null || cursorId > maxCursorId) maxCursorId = cursorId;
}
return maxCursorId;
}
getMaxTraceStreamCursorId() {
let maxCursorId = null;
for (const cursorId of this.db.traceCursorIds.values()) if (maxCursorId === null || cursorId > maxCursorId) maxCursorId = cursorId;
return maxCursorId;
}
getBranchCursorId(key, filters) {
const cursorId = this.db.branchCursorIds.get(key);
if (cursorId === void 0) return null;
const [traceId, spanId] = key.split("\0");
if (!traceId || !spanId) return null;
const span = this.db.traces.get(traceId)?.spans[spanId];
if (!span || !this.spanMatchesBranchFilters(span, filters)) return null;
return cursorId;
}
getMaxBranchCursorId(filters) {
let maxCursorId = null;
for (const key of this.db.branchCursorIds.keys()) {
const cursorId = this.getBranchCursorId(key, filters);
if (cursorId === null) continue;
if (maxCursorId === null || cursorId > maxCursorId) maxCursorId = cursorId;
}
return maxCursorId;
}
getMaxBranchStreamCursorId() {
let maxCursorId = null;
for (const cursorId of this.db.branchCursorIds.values()) if (maxCursorId === null || cursorId > maxCursorId) maxCursorId = cursorId;
return maxCursorId;
}
async createSpan(args) {
const { span } = args;
this.validateCreateSpan(span);
const now = /* @__PURE__ */ new Date();
const record = {
...span,
createdAt: now,
updatedAt: now
};
this.upsertSpanToTrace(record);
}
async batchCreateSpans(args) {
const now = /* @__PURE__ */ new Date();
for (const span of args.records) {
this.validateCreateSpan(span);
const record = {
...span,
createdAt: now,
updatedAt: now
};
this.upsertSpanToTrace(record);
}
}
validateCreateSpan(record) {
if (!record.spanId) throw new MastraError({
id: "OBSERVABILITY_SPAN_ID_REQUIRED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "Span ID is required for creating a span"
});
if (!record.traceId) throw new MastraError({
id: "OBSERVABILITY_TRACE_ID_REQUIRED",
domain: ErrorDomain.MASTRA_OBSERVABILITY,
category: ErrorCategory.SYSTEM,
text: "Trace ID is required for creating a span"
});
}
/**
* Inserts or updates a span in the trace and recomputes trace-level properties
*/
upsertSpanToTrace(span) {
const { traceId, spanId } = span;
let traceEntry = this.db.traces.get(traceId);
if (!traceEntry) {
traceEntry = {
spans: {},
rootSpan: null,
status: "running",
hasChildError: false
};
this.db.traces.set(traceId, traceEntry);
}
traceEntry.spans[spanId] = span;
if (span.parentSpanId == null) traceEntry.rootSpan = span;
this.recomputeTraceProperties(traceEntry);
this.maybeRegisterTraceCursor(traceEntry);
this.maybeRegisterBranchCursor(span);
}
/**
* Recomputes derived trace properties from all spans
*/
recomputeTraceProperties(traceEntry) {
const spans = Object.values(traceEntry.spans);
if (spans.length === 0) return;
traceEntry.hasChildError = spans.some((s) => s.error != null);
const rootSpan = traceEntry.rootSpan;
if (rootSpan) if (rootSpan.error != null) traceEntry.status = "error";
else if (rootSpan.endedAt == null) traceEntry.status = "running";
else traceEntry.status = "success";
else traceEntry.status = "running";
}
async getSpan(args) {
const { traceId, spanId } = args;
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry) return null;
const span = traceEntry.spans[spanId];
if (!span) return null;
return { span };
}
async getSpans(args) {
const { traceId, spanIds } = args;
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry) return {
traceId,
spans: []
};
const spans = [];
for (const spanId of spanIds) {
const span = traceEntry.spans[spanId];
if (span) spans.push(span);
}
return {
traceId,
spans
};
}
async getRootSpan(args) {
const { traceId } = args;
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry || !traceEntry.rootSpan) return null;
return { span: traceEntry.rootSpan };
}
async getTrace(args) {
const { traceId } = args;
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry) return null;
const spans = Object.values(traceEntry.spans);
if (spans.length === 0) return null;
spans.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
return {
traceId,
spans
};
}
async getTraceLight(args) {
const { traceId } = args;
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry) return null;
const spans = Object.values(traceEntry.spans);
if (spans.length === 0) return null;
spans.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
return {
traceId,
spans: spans.map((span) => ({
traceId: span.traceId,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
name: span.name,
spanType: span.spanType,
isEvent: span.isEvent,
startedAt: span.startedAt,
endedAt: span.endedAt,
error: span.error,
entityType: span.entityType,
entityId: span.entityId,
entityName: span.entityName,
createdAt: span.createdAt,
updatedAt: span.updatedAt
}))
};
}
getMatchingRootSpans(args) {
const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
const matchingRootSpans = [];
for (const [, traceEntry] of this.db.traces) {
if (!traceEntry.rootSpan) continue;
if (this.traceMatchesFilters(traceEntry, filters)) matchingRootSpans.push(traceEntry.rootSpan);
}
const { field: sortField, direction: sortDirection } = orderBy;
matchingRootSpans.sort((a, b) => {
if (sortField === "endedAt") {
const aVal = a.endedAt;
const bVal = b.endedAt;
if (aVal == null && bVal == null) return 0;
if (aVal == null) return sortDirection === "DESC" ? -1 : 1;
if (bVal == null) return sortDirection === "DESC" ? 1 : -1;
const diff = aVal.getTime() - bVal.getTime();
return sortDirection === "DESC" ? -diff : diff;
} else {
const diff = a.startedAt.getTime() - b.startedAt.getTime();
return sortDirection === "DESC" ? -diff : diff;
}
});
const total = matchingRootSpans.length;
const { page, perPage } = pagination;
const start = page * perPage;
const end = start + perPage;
return {
paged: matchingRootSpans.slice(start, end),
total,
page,
perPage,
hasMore: end < total
};
}
async listTraces(args) {
const { mode, filters, after, limit } = listTracesArgsSchema.parse(args);
if (mode === "delta") {
this.assertDeltaPollingEnabled();
const fallbackCursorId = this.getMaxTraceCursorId(filters) ?? this.getMaxTraceStreamCursorId();
if (after === void 0) return {
spans: [],
delta: {
limit,
hasMore: false
},
deltaCursor: this.encodeDeltaCursor(fallbackCursorId)
};
const afterCursorId = this.decodeDeltaCursor(after);
const matchingRootSpans = Array.from(this.db.traceCursorIds.entries()).flatMap(([traceId, cursorId]) => {
if (cursorId <= afterCursorId) return [];
const traceEntry = this.db.traces.get(traceId);
if (!traceEntry?.rootSpan || !this.traceMatchesFilters(traceEntry, filters)) return [];
return [{
cursorId,
row: traceEntry.rootSpan
}];
}).sort((a, b) => a.cursorId - b.cursorId).slice(0, limit + 1);
const deltaResponse = this.buildDeltaResponse(matchingRootSpans, limit, fallbackCursorId);
return {
spans: toTraceSpans(deltaResponse.rows),
delta: deltaResponse.delta,
deltaCursor: deltaResponse.deltaCursor
};
}
const { paged, total, page, perPage, hasMore } = this.getMatchingRootSpans(args);
return {
spans: toTraceSpans(paged),
pagination: {
total,
page,
perPage,
hasMore
},
...this.pageDeltaCursor(this.getMaxTraceCursorId(filters) ?? this.getMaxTraceStreamCursorId())
};
}
async listTracesLig