UNPKG

@mastra/core

Version:
1 lines 581 kB
{"version":3,"file":"storage-DBpVHkrA.cjs","names":["StorageDomain","#storage","#domains","#readyDomains","#domainErrors","#domainInitPromises","#ensureStorageReady","#initDomain","#storageReady","#storageInitPromise","StorageDomain","MastraError","ErrorDomain","ErrorCategory","getBranchArgsSchema","extractBranchSpans","SAFE_METADATA_KEY_PATTERN","MAX_METADATA_KEY_LENGTH","DISALLOWED_METADATA_KEYS","TABLE_SCHEMAS","TABLE_SCORERS","coreFeatures","MastraError","ErrorDomain","ErrorCategory","BRANCH_SPAN_TYPE_SET","listTracesArgsSchema","toTraceSpans","listBranchesArgsSchema","toTraceSpan","listMetricsArgsSchema","EntityType","listLogsArgsSchema","listScoresArgsSchema","listFeedbackArgsSchema","EntityType","StorageDomain","MastraBase","#blobs","StorageDomain","#installations","#configs","z","MastraError","MastraError","StorageDomain","MastraError","ErrorDomain","ErrorCategory","createDatasetItemBatchPlan","matchesTenancy","normalizePerPage","calculatePagination","StorageDomain","normalizePerPage","calculatePagination","StorageDomain","#sessions","StorageDomain","normalizePerPage","calculatePagination","MessageList","StorageDomain","StorageDomain","MastraError","ErrorDomain","ErrorCategory","normalizePerPage","calculatePagination","MastraBase","StorageDomain","StorageDomain","normalizePerPage","MastraCompositeStore","#db","InMemoryDB","InMemoryAgentsStorage","InMemoryNotificationsStorage","InMemoryPromptBlocksStorage","InMemoryScorerDefinitionsStorage","InMemoryMCPClientsStorage","InMemoryMCPServersStorage","InMemoryWorkspacesStorage","InMemorySkillsStorage","InMemoryFavoritesStorage","InMemoryThreadStateStorage","sep","MastraCompositeStore","#dir","#db","FilesystemAgentsStorage","FilesystemPromptBlocksStorage","FilesystemScorerDefinitionsStorage","FilesystemMCPClientsStorage","FilesystemMCPServersStorage","FilesystemWorkspacesStorage","FilesystemSkillsStorage","MastraBase","MastraError","ErrorDomain","ErrorCategory"],"sources":["../src/storage/factory-storage.ts","../src/storage/domains/observability/base.ts","../src/storage/utils.ts","../src/storage/domains/observability/inmemory.ts","../src/storage/domains/observability/record-builders.ts","../src/storage/domains/background-tasks/base.ts","../src/storage/domains/background-tasks/inmemory.ts","../src/storage/domains/blobs/base.ts","../src/storage/domains/blobs/inmemory.ts","../src/storage/domains/channels/base.ts","../src/storage/domains/channels/inmemory.ts","../src/datasets/validation/errors.ts","../src/datasets/validation/validator.ts","../../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@/fast-deep-equal/3.1.3/57fbe5fd6f7d3bd61519466ad102884cc9e5511fabd9777317b6582805433878/node_modules/fast-deep-equal/index.js","../src/storage/domains/datasets/identity.ts","../src/storage/domains/datasets/serialization.ts","../src/storage/domains/datasets/base.ts","../src/storage/domains/datasets/inmemory.ts","../src/storage/domains/experiments/base.ts","../src/storage/domains/experiments/inmemory.ts","../src/storage/domains/harness/base.ts","../src/storage/domains/harness/inmemory.ts","../src/storage/domains/memory/base.ts","../src/storage/domains/memory/inmemory.ts","../src/storage/domains/schedules/base.ts","../src/storage/domains/schedules/inmemory.ts","../src/storage/domains/scores/base.ts","../src/storage/domains/scores/inmemory.ts","../src/storage/domains/tool-provider-connections/base.ts","../src/storage/domains/tool-provider-connections/inmemory.ts","../src/storage/domains/workflow-definitions/base.ts","../src/storage/domains/workflow-definitions/inmemory.ts","../src/storage/workflow-snapshot.ts","../src/storage/domains/workflows/base.ts","../src/storage/domains/workflows/inmemory.ts","../src/storage/mock.ts","../src/storage/filesystem-db.ts","../src/storage/filesystem.ts","../src/storage/providers/github.ts","../src/storage/domains/operations/base.ts","../src/storage/domains/operations/inmemory.ts"],"sourcesContent":["/**\n * FactoryStorage — a pluggable application-storage backend contract.\n *\n * One `FactoryStorage` instance powers both sides of an application\n * deployment's persistence:\n *\n * - **Agent state** (threads, messages, memory, observational memory) via\n * {@link FactoryStorage.getMastraStorage}, which callers feed to the\n * Mastra instance and all agent-related wiring.\n * - **App tables** (application-owned collections: settings, audit trails,\n * work items, integration state, ...) via the generic\n * {@link FactoryStorageOps} query surface plus declarative\n * {@link CollectionSchema} DDL mapping.\n *\n * App-table domains are written once against `ops`; backends implement the\n * small query surface once (M + N, not M × N). Nothing outside a backend\n * implementation may branch on the database dialect — optional capabilities\n * such as {@link FactoryStorage.authDatabase} are feature-gated on presence.\n *\n * Contract discipline: the ops surface is deliberately small — equality-filter\n * CRUD, conflict-key upsert, ordered/limit/keyset-cursor lists, and atomic\n * read-modify-write. Anything not expressible here is a deliberate, reviewed\n * contract extension — never raw SQL from a domain.\n *\n * Store packages (`@mastra/pg`, `@mastra/libsql`) ship implementations next\n * to their `MastraCompositeStore` adapters, sharing one connection between\n * agent state and app tables.\n */\n\nimport type { MastraCompositeStore } from './base';\nimport { StorageDomain } from './domains/base';\n\n/** Values storable in (and filterable on) a collection column. */\nexport type CollectionValue = string | number | boolean | Date | null;\n\n/**\n * Row filter: column → required value. Multiple entries AND together.\n * - A {@link CollectionValue} matches by equality; `null` matches SQL `IS NULL`.\n * - `{ in: [...] }` matches any of the listed values (SQL `IN`).\n * - `{}` matches every row.\n *\n * Column names must be declared in the collection's schema — backends reject\n * unknown collections/columns instead of interpolating them.\n */\nexport type CollectionWhere = Record<string, CollectionValue | { in: CollectionValue[] }>;\n\n/**\n * Keyset cursor for stable pagination: the `orderBy` column values of the\n * last row of the previous page, in the same order as `orderBy`. The next\n * page contains rows strictly after that position in the sort order.\n */\nexport interface CollectionCursor {\n values: CollectionValue[];\n}\n\nexport interface CollectionListOptions {\n /** Sort order; required when `cursor` is set. */\n orderBy?: [column: string, dir: 'asc' | 'desc'][];\n limit?: number;\n /** Keyset cursor over the `orderBy` columns (see {@link CollectionCursor}). */\n cursor?: CollectionCursor;\n}\n\n/**\n * Closed column-type union, mapped to backend-native types.\n *\n * `uuid-pk` declares the collection's generated primary key: the ops layer\n * assigns a UUID client-side on insert when the caller doesn't provide one,\n * so every backend produces identical rows. A collection may instead mark one\n * caller-supplied column with `primaryKey: true` (natural keys, e.g. a\n * session id).\n *\n * Value normalization is part of the contract regardless of dialect:\n * `timestamp` columns round-trip as `Date`, `json` as parsed values,\n * `boolean` as booleans, and `bigint` as JS numbers (safe integers — e.g.\n * GitHub ids fit well inside 2^53).\n */\nexport type CollectionColumnType = 'text' | 'bigint' | 'integer' | 'boolean' | 'json' | 'timestamp' | 'uuid-pk';\n\nexport interface CollectionColumnSpec {\n type: CollectionColumnType;\n /** Columns are NOT NULL unless marked nullable. */\n nullable?: boolean;\n /**\n * Natural primary key (caller-supplied on insert). Mutually exclusive with\n * a `uuid-pk` column; exactly one primary key per collection.\n */\n primaryKey?: boolean;\n /**\n * DDL-level default literal. Required when additively introducing a\n * NOT NULL column to a collection that may already have rows (e.g.\n * `actor_type text NOT NULL DEFAULT 'human'`).\n */\n default?: string | number | boolean;\n}\n\n/**\n * Unique index. The optional partial forms cover the two shapes app schemas\n * need: `whereNotNull` (unique per non-null natural key) and `whereNull`\n * (unique per scope where an owner column is absent).\n */\nexport interface CollectionUniqueIndexSpec {\n name: string;\n columns: string[];\n /** Index only rows where this column IS NOT NULL. */\n whereNotNull?: string;\n /** Index only rows where this column IS NULL. */\n whereNull?: string;\n}\n\nexport interface CollectionIndexSpec {\n name: string;\n columns: string[];\n}\n\n/**\n * Declarative collection definition, mapped to backend DDL by\n * {@link FactoryStorage.ensureCollections}. Evolution is additive only:\n * re-running with new columns/indexes adds them; nothing is dropped or\n * retyped.\n */\nexport interface CollectionSchema {\n name: string;\n /** Column name → spec. Rows returned by ops are keyed by these names. */\n columns: Record<string, CollectionColumnSpec>;\n uniqueIndexes?: CollectionUniqueIndexSpec[];\n indexes?: CollectionIndexSpec[];\n}\n\n/**\n * Tagged database handle for auth libraries (e.g. better-auth). Consumers\n * narrow on `dialect` to build their driver adapter — a supported contract,\n * unlike sniffing store internals. `custom` passes an adapter/instance the\n * auth library accepts as-is.\n */\nexport type FactoryAuthDatabase =\n | { dialect: 'postgres'; pool: unknown }\n | { dialect: 'libsql'; client: unknown }\n | { dialect: 'custom'; database: unknown };\n\n/**\n * Thrown by `insertOne`/`upsertOne` when a unique constraint rejects the row.\n * Backends map their native duplicate-key errors onto this type so domains\n * can implement insert-or-recover races portably.\n */\nexport class UniqueViolationError extends Error {\n readonly collection: string;\n\n constructor(collection: string, options?: { cause?: unknown }) {\n super(`Unique constraint violation on collection '${collection}'`, options);\n this.name = 'UniqueViolationError';\n this.collection = collection;\n }\n}\n\n/**\n * The generic query surface app-table domains are written against.\n *\n * Rows (`T`) are plain objects keyed by schema column names; domains own any\n * mapping to their public camelCase shapes. All methods throw if the\n * collection (or any referenced column) was not registered via\n * `ensureCollections`.\n */\nexport interface FactoryStorageOps {\n findOne<T extends Record<string, unknown>>(collection: string, where: CollectionWhere): Promise<T | null>;\n\n findMany<T extends Record<string, unknown>>(\n collection: string,\n where: CollectionWhere,\n opts?: CollectionListOptions,\n ): Promise<T[]>;\n\n /**\n * Insert one row, returning it (with the generated `uuid-pk` populated).\n * Throws {@link UniqueViolationError} on any unique-constraint conflict.\n */\n insertOne<T extends Record<string, unknown>>(collection: string, row: Partial<T>): Promise<T>;\n\n /**\n * Insert, or update the existing row that matches `conflictKeys` (which\n * must be covered by a unique index). Non-key columns present in `row`\n * replace the stored values; the existing primary key is preserved.\n */\n upsertOne<T extends Record<string, unknown>>(collection: string, conflictKeys: string[], row: Partial<T>): Promise<T>;\n\n /** Set columns on every matching row. Returns the number of rows updated. */\n updateMany(collection: string, where: CollectionWhere, set: Record<string, unknown>): Promise<number>;\n\n /** Delete every matching row. Returns the number of rows deleted. */\n deleteMany(collection: string, where: CollectionWhere): Promise<number>;\n\n /**\n * Atomic read-modify-write of one matching row. `fn` receives the current\n * row and returns the columns to set — or `null` to abort without writing\n * (the unmodified row is returned; use a closure flag to distinguish abort\n * from success). Returns `null` when no row matches.\n *\n * Isolation: pg runs `fn` inside a `SELECT ... FOR UPDATE` transaction;\n * libsql serializes through its single-writer path. Either way, concurrent\n * `updateAtomic` calls on the same row never lose each other's writes.\n */\n updateAtomic<T extends Record<string, unknown>>(\n collection: string,\n where: CollectionWhere,\n fn: (row: T) => Partial<T> | null | Promise<Partial<T> | null>,\n ): Promise<T | null>;\n}\n\n/**\n * Base class for application domains owned by a {@link FactoryStorage}.\n * Domains are bound once when registered and share their owner's connection.\n */\nexport abstract class FactoryStorageDomain extends StorageDomain {\n override readonly name: string;\n #storage?: FactoryStorage;\n\n protected constructor(name: string) {\n if (!name.trim()) {\n throw new Error('Factory storage domain name must not be empty');\n }\n super({ component: 'STORAGE', name });\n this.name = name;\n }\n\n /** @internal Bound by {@link FactoryStorage.registerDomain}. */\n __bindFactoryStorage(storage: FactoryStorage): void {\n if (this.#storage && this.#storage !== storage) {\n throw new Error(`Factory storage domain '${this.name}' is already bound to another storage instance`);\n }\n this.#storage = storage;\n }\n\n protected get storage(): FactoryStorage {\n if (!this.#storage) {\n throw new Error(`Factory storage domain '${this.name}' has not been registered`);\n }\n return this.#storage;\n }\n\n /**\n * Initialize this domain (via its owning storage) if it hasn't been yet.\n * Lets consumers holding a domain handle run the same fail-soft readiness\n * check as {@link FactoryStorage.ensureDomainReady} without also needing a\n * reference to the storage backend.\n */\n ensureReady(): Promise<void> {\n return this.storage.ensureDomainReady(this.name);\n }\n\n protected get ops(): FactoryStorageOps {\n return this.storage.ops;\n }\n\n protected ensureCollections(schemas: CollectionSchema[]): Promise<void> {\n return this.storage.ensureCollections(schemas);\n }\n}\n\n/**\n * A pluggable application-storage backend: one database powering agent state\n * (via {@link getMastraStorage}) and app-owned collections (via {@link ops}).\n */\nexport abstract class FactoryStorage {\n readonly #domains = new Map<string, FactoryStorageDomain>();\n readonly #readyDomains = new Set<string>();\n readonly #domainErrors = new Map<string, unknown>();\n readonly #domainInitPromises = new Map<string, Promise<void>>();\n #storageReady = false;\n #storageInitPromise?: Promise<void>;\n\n /**\n * Agent-state store (threads, messages, memory, OM) for this database,\n * sharing this backend's connection. Callers pass the result to the Mastra\n * instance and all agent-related wiring. Lazily constructed; returns the\n * same instance on repeat calls.\n */\n abstract getMastraStorage(): MastraCompositeStore;\n\n /** Open/validate the backend, then initialize registered domains fail-soft. */\n async init(): Promise<void> {\n await this.#ensureStorageReady();\n await Promise.all([...this.#domains.keys()].map(name => this.#initDomain(name).catch(() => undefined)));\n }\n\n /** Backend-specific connection initialization. */\n protected abstract initStorage(): Promise<void>;\n\n registerDomain<T extends FactoryStorageDomain>(domain: T): T {\n if (this.#domains.has(domain.name)) {\n throw new Error(`Factory storage domain '${domain.name}' is already registered`);\n }\n domain.__bindFactoryStorage(this);\n this.#domains.set(domain.name, domain);\n return domain;\n }\n\n getDomain<T extends FactoryStorageDomain = FactoryStorageDomain>(name: string): T {\n const domain = this.#domains.get(name);\n if (!domain) {\n throw new Error(`Factory storage domain '${name}' is not registered`);\n }\n return domain as T;\n }\n\n hasDomain(name: string): boolean {\n return this.#domains.has(name);\n }\n\n domainNames(): string[] {\n return [...this.#domains.keys()];\n }\n\n isDomainReady(name: string): boolean {\n return this.#readyDomains.has(name);\n }\n\n domainInitError(name: string): unknown {\n return this.#domainErrors.get(name);\n }\n\n async ensureDomainReady(name: string): Promise<void> {\n this.getDomain(name);\n await this.#ensureStorageReady();\n await this.#initDomain(name);\n }\n\n /**\n * Map each domain's declarative schema to backend DDL. Idempotent and\n * additive: safe to re-run, never drops or retypes anything. Registers the\n * schemas so `ops` can validate identifiers and normalize values.\n */\n abstract ensureCollections(schemas: CollectionSchema[]): Promise<void>;\n\n /** The generic query surface domains are written against. */\n abstract readonly ops: FactoryStorageOps;\n\n /**\n * Run a group of app-table operations atomically. The callback receives an\n * ops instance bound to the transaction; callers must not use `this.ops`\n * inside it. Serializable callbacks may be retried after a serialization\n * failure and therefore must contain database operations only.\n */\n abstract withTransaction<T>(\n fn: (ops: FactoryStorageOps) => Promise<T>,\n options?: { isolationLevel?: 'serializable' },\n ): Promise<T>;\n\n /** Release the backend's connections (tests, shutdown). */\n abstract close(): Promise<void>;\n\n // ---- optional capabilities (feature-gate on presence, never on dialect) ----\n\n /**\n * A tagged database handle auth libraries can consume (see\n * {@link FactoryAuthDatabase}). Absent → auth integrations require a\n * user-provided instance.\n */\n authDatabase?(): FactoryAuthDatabase;\n\n async #ensureStorageReady(): Promise<void> {\n if (this.#storageReady) return;\n if (this.#storageInitPromise) return this.#storageInitPromise;\n\n const initPromise = (async () => {\n await this.initStorage();\n this.#storageReady = true;\n })();\n this.#storageInitPromise = initPromise;\n\n try {\n await initPromise;\n } finally {\n if (this.#storageInitPromise === initPromise) {\n this.#storageInitPromise = undefined;\n }\n }\n }\n\n #initDomain(name: string): Promise<void> {\n if (this.#readyDomains.has(name)) return Promise.resolve();\n const pending = this.#domainInitPromises.get(name);\n if (pending) return pending;\n\n const domain = this.getDomain(name);\n this.#domainErrors.delete(name);\n const initPromise = (async () => {\n try {\n await domain.init();\n this.#readyDomains.add(name);\n } catch (error) {\n this.#domainErrors.set(name, error);\n throw error;\n } finally {\n this.#domainInitPromises.delete(name);\n }\n })();\n this.#domainInitPromises.set(name, initPromise);\n return initPromise;\n }\n}\n","import { ErrorCategory, ErrorDomain, MastraError } from '../../../error';\nimport { StorageDomain } from '../base';\nimport type {\n GetEntityTypesArgs,\n GetEntityTypesResponse,\n GetEntityNamesArgs,\n GetEntityNamesResponse,\n GetServiceNamesArgs,\n GetServiceNamesResponse,\n GetEnvironmentsArgs,\n GetEnvironmentsResponse,\n GetTagsArgs,\n GetTagsResponse,\n GetMetricNamesArgs,\n GetMetricNamesResponse,\n GetMetricLabelKeysArgs,\n GetMetricLabelKeysResponse,\n GetMetricLabelValuesArgs,\n GetMetricLabelValuesResponse,\n} from './discovery';\nimport type {\n BatchCreateFeedbackArgs,\n CreateFeedbackArgs,\n ListFeedbackArgs,\n ListFeedbackResponse,\n GetFeedbackAggregateArgs,\n GetFeedbackAggregateResponse,\n GetFeedbackBreakdownArgs,\n GetFeedbackBreakdownResponse,\n GetFeedbackTimeSeriesArgs,\n GetFeedbackTimeSeriesResponse,\n GetFeedbackPercentilesArgs,\n GetFeedbackPercentilesResponse,\n} from './feedback';\nimport type { BatchCreateLogsArgs, ListLogsArgs, ListLogsResponse } from './logs';\nimport type {\n BatchCreateMetricsArgs,\n ListMetricsArgs,\n ListMetricsResponse,\n GetMetricAggregateArgs,\n GetMetricAggregateResponse,\n GetMetricBreakdownArgs,\n GetMetricBreakdownResponse,\n GetMetricTimeSeriesArgs,\n GetMetricTimeSeriesResponse,\n GetMetricPercentilesArgs,\n GetMetricPercentilesResponse,\n} from './metrics';\nimport type {\n BatchCreateScoresArgs,\n CreateScoreArgs,\n ListScoresArgs,\n ListScoresResponse,\n ScoreRecord,\n GetScoreAggregateArgs,\n GetScoreAggregateResponse,\n GetScoreBreakdownArgs,\n GetScoreBreakdownResponse,\n GetScoreTimeSeriesArgs,\n GetScoreTimeSeriesResponse,\n GetScorePercentilesArgs,\n GetScorePercentilesResponse,\n} from './scores';\nimport type {\n BatchCreateSpansArgs,\n BatchDeleteTracesArgs,\n BatchUpdateSpansArgs,\n CreateSpanArgs,\n GetBranchArgs,\n GetBranchResponse,\n GetRootSpanArgs,\n GetRootSpanResponse,\n GetSpanArgs,\n GetSpanResponse,\n GetSpansArgs,\n GetSpansResponse,\n GetStructureResponse,\n GetTraceArgs,\n GetTraceResponse,\n GetTraceLightResponse,\n ListBranchesArgs,\n ListBranchesResponse,\n ListTracesArgs,\n ListTracesLightResponse,\n ListTracesResponse,\n UpdateSpanArgs,\n} from './tracing';\nimport { extractBranchSpans, getBranchArgsSchema } from './tracing';\nimport type { ObservabilityStorageStrategy, TracingStorageStrategy } from './types';\n\nexport type ObservabilityStorageFeature = 'delta-polling' | 'metrics' | 'logs';\n\n/**\n * Base storage class for observability data (traces, metrics, logs, scores, feedback).\n * Not abstract -- provides default implementations that throw \"not implemented\" errors.\n * Storage adapters override only the methods they support.\n */\nexport class ObservabilityStorage extends StorageDomain {\n constructor() {\n super({\n component: 'STORAGE',\n name: 'OBSERVABILITY',\n });\n }\n\n async dangerouslyClearAll(): Promise<void> {\n // Default no-op - subclasses override\n }\n\n /**\n * Provides hints for tracing strategy selection by the MastraStorageExporter.\n * Storage adapters can override this to specify their preferred and supported strategies.\n */\n public get observabilityStrategy(): {\n preferred: ObservabilityStorageStrategy;\n supported: ObservabilityStorageStrategy[];\n } {\n return {\n preferred: 'batch-with-updates', // Default for most SQL stores\n supported: ['realtime', 'batch-with-updates', 'insert-only'],\n };\n }\n\n /**\n * Provides hints for tracing strategy selection by the MastraStorageExporter.\n * Storage adapters can override this to specify their preferred and supported strategies.\n * @deprecated Use {@link observabilityStrategy} instead.\n * @see {@link observabilityStrategy} for the replacement property.\n */\n public get tracingStrategy(): {\n preferred: TracingStorageStrategy;\n supported: TracingStorageStrategy[];\n } {\n return this.observabilityStrategy;\n }\n\n /**\n * Reports the tracing strategy currently in effect for this attached observability store.\n *\n * Single-strategy stores can rely on the default implementation. Multi-strategy stores\n * should override this getter only when they can determine the actual configured mode\n * from storage-owned configuration, not exporter state.\n */\n public get runtimeTracingStrategy(): TracingStorageStrategy | undefined {\n const supportedStrategies = this.observabilityStrategy.supported;\n return supportedStrategies.length === 1 ? supportedStrategies[0] : undefined;\n }\n\n /**\n * Optional feature list for observability storage APIs.\n * Stores should override this to opt in to the APIs they support explicitly.\n * Older stores and older package versions will simply omit it, which keeps page mode working.\n */\n public getFeatures(): readonly ObservabilityStorageFeature[] | undefined {\n return undefined;\n }\n\n /**\n * Creates a single Span record in the storage provider.\n */\n async createSpan(_args: CreateSpanArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_CREATE_SPAN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support creating spans',\n });\n }\n\n /**\n * Updates a single Span with partial data. Primarily used for realtime trace creation.\n *\n * @deprecated This method only works with stores that support span updates,\n * It will be removed in the future. Instead try to add all data to a span before\n * ending it.\n */\n async updateSpan(_args: UpdateSpanArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_UPDATE_SPAN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support updating spans',\n });\n }\n\n /**\n * Retrieves a single span.\n */\n async getSpan(_args: GetSpanArgs): Promise<GetSpanResponse | null> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SPAN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting spans',\n });\n }\n\n /**\n * Retrieves a single root span.\n */\n async getRootSpan(_args: GetRootSpanArgs): Promise<GetRootSpanResponse | null> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_ROOT_SPAN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting root spans',\n });\n }\n\n /**\n * Retrieves a single trace with all its associated spans.\n */\n async getTrace(_args: GetTraceArgs): Promise<GetTraceResponse | null> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_TRACE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting traces',\n });\n }\n\n /**\n * Retrieves the structural skeleton of a trace -- parent/child links, span\n * type, timing, and status -- with heavy fields (input, output, attributes,\n * metadata, tags, links) excluded. Intended for waterfall/timeline rendering\n * where the full payload would be wasteful.\n *\n * Default implementation forwards to {@link getTraceLight} (the legacy\n * override surface). Backends should override either method -- the response\n * shape is identical, and the unimplemented one delegates to the\n * implemented one. The cycle guard is what makes that safe.\n */\n async getStructure(args: GetTraceArgs): Promise<GetStructureResponse | null> {\n if (this.getTraceLight === ObservabilityStorage.prototype.getTraceLight) {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_STRUCTURE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting trace structure',\n });\n }\n return this.getTraceLight(args);\n }\n\n /**\n * @deprecated Use {@link getStructure} instead. Default implementation\n * forwards to {@link getStructure} so backends that only override the\n * canonical name still work for legacy callers.\n */\n async getTraceLight(args: GetTraceArgs): Promise<GetTraceLightResponse | null> {\n if (this.getStructure === ObservabilityStorage.prototype.getStructure) {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_TRACE_LIGHT_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting lightweight traces',\n });\n }\n return this.getStructure(args);\n }\n\n /**\n * Retrieves the subtree of spans rooted at a given span, optionally bounded\n * to `depth` levels of descendants.\n *\n * Default implementation prefers a two-step path: fetch the lightweight\n * structure to determine which spans belong to the branch, then batch-fetch\n * only those with full data. This avoids pulling the entire trace when the\n * branch is a small slice of a large trace. Backends that don't yet\n * implement {@link getStructure} or {@link getSpans} fall back to fetching\n * the full trace and walking it in memory.\n */\n async getBranch(args: GetBranchArgs): Promise<GetBranchResponse | null> {\n const parsed = getBranchArgsSchema.parse(args);\n\n // Optimized path: skeleton walk → batch fetch the branch's spans.\n try {\n const skeleton = await this.getStructure({ traceId: parsed.traceId });\n if (!skeleton) return null;\n const branchSpanIds = extractBranchSpans(skeleton.spans, parsed.spanId, parsed.depth).map(s => s.spanId);\n if (branchSpanIds.length === 0) return null;\n const { spans } = await this.getSpans({ traceId: parsed.traceId, spanIds: branchSpanIds });\n if (spans.length === 0) return null;\n spans.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());\n return { traceId: parsed.traceId, spans };\n } catch (error) {\n const isFallbackTrigger =\n error instanceof MastraError &&\n (error.id === 'OBSERVABILITY_STORAGE_GET_STRUCTURE_NOT_IMPLEMENTED' ||\n error.id === 'OBSERVABILITY_STORAGE_GET_TRACE_LIGHT_NOT_IMPLEMENTED' ||\n error.id === 'OBSERVABILITY_STORAGE_GET_SPANS_NOT_IMPLEMENTED');\n if (!isFallbackTrigger) throw error;\n }\n\n // Fallback: pull the whole trace, walk in memory.\n const trace = await this.getTrace({ traceId: parsed.traceId });\n if (!trace) return null;\n const spans = extractBranchSpans(trace.spans, parsed.spanId, parsed.depth);\n if (spans.length === 0) return null;\n return { traceId: parsed.traceId, spans };\n }\n\n /**\n * Batch-fetches spans by spanId within a single trace. Used by the\n * optimized {@link getBranch} path to fetch only the spans that belong to\n * the requested branch (after walking the lightweight structure to identify\n * them) instead of pulling the entire trace.\n */\n async getSpans(_args: GetSpansArgs): Promise<GetSpansResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SPANS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch-fetching spans',\n });\n }\n\n /**\n * Retrieves a list of traces with optional filtering.\n */\n async listTraces(_args: ListTracesArgs): Promise<ListTracesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_TRACES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing traces',\n });\n }\n\n /**\n * Retrieves a lightweight list of traces with optional filtering.\n */\n async listTracesLight(_args: ListTracesArgs): Promise<ListTracesLightResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_TRACES_LIGHT_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing lightweight traces',\n });\n }\n\n /**\n * Lists trace branches across all traces. Unlike {@link listTraces} (which\n * returns one row per root-rooted trace), each row here is a single branch\n * anchor span, including ones nested under a different root entity -- useful\n * for \"show me every run of agent X\" regardless of caller. Pairs with\n * {@link getBranch} to expand a single branch into its subtree.\n */\n async listBranches(_args: ListBranchesArgs): Promise<ListBranchesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_BRANCHES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing trace branches',\n });\n }\n\n /**\n * Creates multiple Spans in a single batch.\n */\n async batchCreateSpans(_args: BatchCreateSpansArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_CREATE_SPAN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch creating spans',\n });\n }\n\n /**\n * Updates multiple Spans in a single batch.\n */\n async batchUpdateSpans(_args: BatchUpdateSpansArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_UPDATE_SPANS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch updating spans',\n });\n }\n\n /**\n * Deletes multiple traces and all their associated spans in a single batch operation.\n */\n async batchDeleteTraces(_args: BatchDeleteTracesArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_DELETE_TRACES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch deleting traces',\n });\n }\n\n // ============================================================================\n // Logs\n // ============================================================================\n\n /**\n * Creates multiple log records in a single batch.\n */\n async batchCreateLogs(_args: BatchCreateLogsArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_CREATE_LOGS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch creating logs',\n });\n }\n\n /**\n * Retrieves a list of logs with optional filtering.\n */\n async listLogs(_args: ListLogsArgs): Promise<ListLogsResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_LOGS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing logs',\n });\n }\n\n // ============================================================================\n // Metrics\n // ============================================================================\n\n /**\n * Creates multiple metric observations in a single batch.\n */\n async batchCreateMetrics(_args: BatchCreateMetricsArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_CREATE_METRICS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch creating metrics',\n });\n }\n\n async listMetrics(_args: ListMetricsArgs): Promise<ListMetricsResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_METRICS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing metrics',\n });\n }\n\n async getMetricAggregate(_args: GetMetricAggregateArgs): Promise<GetMetricAggregateResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_AGGREGATE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric aggregation',\n });\n }\n\n async getMetricBreakdown(_args: GetMetricBreakdownArgs): Promise<GetMetricBreakdownResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_BREAKDOWN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric breakdown',\n });\n }\n\n async getMetricTimeSeries(_args: GetMetricTimeSeriesArgs): Promise<GetMetricTimeSeriesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_TIME_SERIES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric time series',\n });\n }\n\n async getMetricPercentiles(_args: GetMetricPercentilesArgs): Promise<GetMetricPercentilesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_PERCENTILES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric percentiles',\n });\n }\n\n // ============================================================================\n // Discovery / Metadata Methods\n // ============================================================================\n\n async getMetricNames(_args: GetMetricNamesArgs): Promise<GetMetricNamesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_NAMES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric name discovery',\n });\n }\n\n async getMetricLabelKeys(_args: GetMetricLabelKeysArgs): Promise<GetMetricLabelKeysResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_METRIC_LABEL_KEYS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support metric label key discovery',\n });\n }\n\n async getMetricLabelValues(_args: GetMetricLabelValuesArgs): Promise<GetMetricLabelValuesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_LABEL_VALUES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support label value discovery',\n });\n }\n\n async getEntityTypes(_args: GetEntityTypesArgs): Promise<GetEntityTypesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_ENTITY_TYPES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support entity type discovery',\n });\n }\n\n async getEntityNames(_args: GetEntityNamesArgs): Promise<GetEntityNamesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_ENTITY_NAMES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support entity name discovery',\n });\n }\n\n async getServiceNames(_args: GetServiceNamesArgs): Promise<GetServiceNamesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SERVICE_NAMES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support service name discovery',\n });\n }\n\n async getEnvironments(_args: GetEnvironmentsArgs): Promise<GetEnvironmentsResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_ENVIRONMENTS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support environment discovery',\n });\n }\n\n async getTags(_args: GetTagsArgs): Promise<GetTagsResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_TAGS_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support tag discovery',\n });\n }\n\n // ============================================================================\n // Scores\n // ============================================================================\n\n /**\n * Creates a single score record.\n */\n async createScore(_args: CreateScoreArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_CREATE_SCORE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support creating scores',\n });\n }\n\n /**\n * Creates multiple score observations in a single batch.\n */\n async batchCreateScores(_args: BatchCreateScoresArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_CREATE_SCORES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch creating scores',\n });\n }\n\n /**\n * Retrieves a list of scores with optional filtering.\n */\n async listScores(_args: ListScoresArgs): Promise<ListScoresResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_SCORES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing scores',\n });\n }\n\n /**\n * Retrieves a single score by its score ID.\n */\n async getScoreById(_scoreId: string): Promise<ScoreRecord | null> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SCORE_BY_ID_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support getting scores by ID',\n });\n }\n\n async getScoreAggregate(_args: GetScoreAggregateArgs): Promise<GetScoreAggregateResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SCORE_AGGREGATE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support score aggregation',\n });\n }\n\n async getScoreBreakdown(_args: GetScoreBreakdownArgs): Promise<GetScoreBreakdownResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SCORE_BREAKDOWN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support score breakdown',\n });\n }\n\n async getScoreTimeSeries(_args: GetScoreTimeSeriesArgs): Promise<GetScoreTimeSeriesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SCORE_TIME_SERIES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support score time series',\n });\n }\n\n async getScorePercentiles(_args: GetScorePercentilesArgs): Promise<GetScorePercentilesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_SCORE_PERCENTILES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support score percentiles',\n });\n }\n\n // ============================================================================\n // Feedback\n // ============================================================================\n\n /**\n * Creates a single feedback record.\n */\n async createFeedback(_args: CreateFeedbackArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_CREATE_FEEDBACK_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support creating feedback',\n });\n }\n\n /**\n * Creates multiple feedback observations in a single batch.\n */\n async batchCreateFeedback(_args: BatchCreateFeedbackArgs): Promise<void> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_BATCH_CREATE_FEEDBACK_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support batch creating feedback',\n });\n }\n\n /**\n * Retrieves a list of feedback with optional filtering.\n */\n async listFeedback(_args: ListFeedbackArgs): Promise<ListFeedbackResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_LIST_FEEDBACK_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support listing feedback',\n });\n }\n\n async getFeedbackAggregate(_args: GetFeedbackAggregateArgs): Promise<GetFeedbackAggregateResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_FEEDBACK_AGGREGATE_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support feedback aggregation',\n });\n }\n\n async getFeedbackBreakdown(_args: GetFeedbackBreakdownArgs): Promise<GetFeedbackBreakdownResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_FEEDBACK_BREAKDOWN_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support feedback breakdown',\n });\n }\n\n async getFeedbackTimeSeries(_args: GetFeedbackTimeSeriesArgs): Promise<GetFeedbackTimeSeriesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_FEEDBACK_TIME_SERIES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support feedback time series',\n });\n }\n\n async getFeedbackPercentiles(_args: GetFeedbackPercentilesArgs): Promise<GetFeedbackPercentilesResponse> {\n throw new MastraError({\n id: 'OBSERVABILITY_STORAGE_GET_FEEDBACK_PERCENTILES_NOT_IMPLEMENTED',\n domain: ErrorDomain.MASTRA_OBSERVABILITY,\n category: ErrorCategory.SYSTEM,\n text: 'This storage provider does not support feedback percentiles',\n });\n }\n}\n","import type { ScoreRowData } from '../evals/types';\nimport { TABLE_SCHEMAS, TABLE_SCORERS } from './constants';\nimport type { TABLE_NAMES } from './constants';\nimport type { Duration } from './retention';\nimport type { StorageColumn, StorageMetadataFilter } from './types';\n\n/**\n * Canonical store names for type safety.\n * Provides autocomplete suggestions while still accepting any string.\n */\nexport type StoreName =\n | 'PG'\n | 'MSSQL'\n | 'LIBSQL'\n | 'MONGODB'\n | 'CLICKHOUSE'\n | 'CLOUDFLARE'\n | 'CLOUDFLARE_D1'\n | 'DYNAMODB'\n | 'LANCE'\n | 'UPSTASH'\n | 'ASTRA'\n | 'CHROMA'\n | 'COUCHBASE'\n | 'OPENSEARCH'\n | 'PINECONE'\n | 'QDRANT'\n | 'S3'\n | 'TURBOPUFFER'\n | 'VECTORIZE'\n | (string & {});\n\nexport function hasErrorCode(error: unknown, codes: ReadonlySet<string | number>): boolean {\n const seen = new Set<object>();\n let current: unknown = error;\n while (current && typeof current === 'object' && !seen.has(current)) {\n seen.add(current);\n if ('code' in current && codes.has((current as { code: string | number }).code)) return true;\n current = 'cause' in current ? (current as { cause?: unknown }).cause : undefined;\n }\n return false;\n}\n\nconst DURATION_UNIT_MS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n w: 7 * 24 * 60 * 60 * 1000,\n};\n\n/**\n * Parses a retention {@link Duration} into milliseconds.\n *\n * Accepts a raw number of milliseconds or a `<number><unit>` string where unit\n * is one of `ms`, `s`, `m`, `h`, `d`, `w`.\n *\n * @throws Error if the input is not a valid duration.\n */\nexport function parseDuration(duration: Duration): number {\n if (typeof duration === 'number') {\n if (!Number.isFinite(duration) || duration < 0) {\n throw new Error(`Invalid retention duration: ${duration}. Must be a non-negative finite number of milliseconds.`);\n }\n return duration;\n }\n\n const match = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d|w)$/.exec(duration);\n if (!match) {\n throw new Error(\n `Invalid retention duration: \"${duration}\". Expected a number of milliseconds or a \"<number><unit>\" string (ms, s, m, h, d, w).`,\n );\n }\n\n const value = Number(match[1]);\n const unit = match[2]!;\n return value * DURATION_UNIT_MS[unit]!;\n}\n\nexport function safelyParseJSON(input: any): any {\n // If already an object (and not null), return as-is\n if (input && typeof input === 'object') return input;\n if (input == null) return {};\n // If it's a string, try to parse\n if (typeof input === 'string') {\n try {\n return JSON.parse(input);\n } catch {\n return input;\n }\n }\n // For anything else (number, boolean, etc.), return empty object\n return {};\n}\n\nconst SAFE_METADATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\nconst MAX_METADATA_KEY_LENGTH = 128;\nconst DISALLOWED_METADATA_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\n\nexport function validateStorageMetadataFilter(\n metadata: StorageMetadataFilter | undefined,\n): StorageMetadataFilter | undefined {\n if (metadata === undefined) return undefined;\n if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {\n throw new TypeError('Metadata filter must be an object.');\n }\n\n const entries = Object.entries(metadata);\n for (const [key, value] of entries) {\n if (\n key.length > MAX_METADATA_KEY_LENGTH ||\n !SAFE_METADATA_KEY_PATTERN.test(key) ||\n DISALLOWED_METAD