UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

854 lines (848 loc) 30.9 kB
'use strict'; var chunkFCQNDFEW_cjs = require('./chunk-FCQNDFEW.cjs'); var child_process = require('child_process'); var fs = require('fs'); var path = require('path'); // src/storage/base.ts var EDITOR_DOMAINS = [ "agents", "promptBlocks", "scorerDefinitions", "mcpClients", "mcpServers", "workspaces", "skills", "favorites" ]; function normalizePerPage(perPageInput, defaultValue) { if (perPageInput === false) { return Number.MAX_SAFE_INTEGER; } else if (perPageInput === 0) { return 0; } else if (typeof perPageInput === "number" && perPageInput > 0) { return perPageInput; } else if (typeof perPageInput === "number" && perPageInput < 0) { throw new Error("perPage must be >= 0"); } return defaultValue; } function calculatePagination(page, perPageInput, normalizedPerPage) { return { offset: perPageInput === false ? 0 : page * normalizedPerPage, perPage: perPageInput === false ? false : normalizedPerPage }; } var MastraCompositeStore = class extends chunkFCQNDFEW_cjs.MastraBase { hasInitialized = null; shouldCacheInit = true; id; stores; /** * When true, automatic initialization (table creation/migrations) is disabled. */ disableInit = false; /** * Retained references to the parent stores supplied via composition. `init()` * delegates to these so the parent's own `init()` logic (pragmas, ordered * DDL, init coalescing, etc.) runs instead of being bypassed by the * composite iterating the inner domains in parallel — which was the cause * of the SQLITE_BUSY / "no such table" races reported in issue #16782. */ parentDefault; parentEditor; constructor(config) { const name = config.name ?? "MastraCompositeStore"; if (!config.id || typeof config.id !== "string" || config.id.trim() === "") { throw new Error(`${name}: id must be provided and cannot be empty.`); } super({ component: "STORAGE", name }); this.id = config.id; this.disableInit = config.disableInit ?? false; if (config.default || config.editor || config.domains) { const defaultStores = config.default?.stores; const editorStores = config.editor?.stores; const domainOverrides = config.domains ?? {}; this.parentDefault = config.default; this.parentEditor = config.editor; const hasDefaultDomains = defaultStores && Object.values(defaultStores).some((v) => v !== void 0); const hasEditorDomains = editorStores && Object.values(editorStores).some((v) => v !== void 0); const hasOverrideDomains = Object.values(domainOverrides).some((v) => v !== void 0); if (!hasDefaultDomains && !hasEditorDomains && !hasOverrideDomains) { throw new Error( "MastraCompositeStore requires at least one storage source. Provide a default storage, an editor storage, or domain overrides." ); } const editorDomainSet = new Set(EDITOR_DOMAINS); const resolve = (key) => { if (domainOverrides[key] !== void 0) return domainOverrides[key]; if (editorDomainSet.has(key) && editorStores?.[key] !== void 0) return editorStores[key]; return defaultStores?.[key]; }; this.stores = { memory: resolve("memory"), workflows: resolve("workflows"), scores: resolve("scores"), observability: resolve("observability"), agents: resolve("agents"), datasets: resolve("datasets"), experiments: resolve("experiments"), promptBlocks: resolve("promptBlocks"), scorerDefinitions: resolve("scorerDefinitions"), mcpClients: resolve("mcpClients"), mcpServers: resolve("mcpServers"), workspaces: resolve("workspaces"), skills: resolve("skills"), favorites: resolve("favorites"), blobs: resolve("blobs"), backgroundTasks: resolve("backgroundTasks"), schedules: resolve("schedules"), channels: resolve("channels") }; } } /** * Get a domain-specific storage interface. * * @param storeName - The name of the domain to access ('memory', 'workflows', 'scores', 'observability', 'agents') * @returns The domain storage interface, or undefined if not available * * @example * ```typescript * const memory = await storage.getStore('memory'); * if (memory) { * await memory.saveThread({ thread }); * } * ``` */ async getStore(storeName) { return this.stores?.[storeName]; } /** * Initialize all domain stores. * * When a parent store was supplied via `default` or `editor`, delegate to * its own `init()` first. Each adapter owns its `init()` contract — it may * apply connection-level setup, run migrations, enforce DDL ordering, or * coalesce concurrent callers. Calling each domain's `init()` directly * against the parent's shared client would bypass all of that and can * corrupt or partially create schema (see issue #16782 for the SQLite * symptom). * * Any remaining domains that did NOT come from a parent (e.g. supplied via * the explicit `domains` override pointing at a different store) are then * initialized individually — but only the ones the parents didn't already * cover, so we never double-init the same domain instance. */ async init() { if (this.shouldCacheInit && await this.hasInitialized) { return; } this.hasInitialized = this.#runInit(); await this.hasInitialized; } async #runInit() { const uniqueParents = /* @__PURE__ */ new Set(); if (this.parentDefault) uniqueParents.add(this.parentDefault); if (this.parentEditor) uniqueParents.add(this.parentEditor); await Promise.all([...uniqueParents].map((parent) => parent.init())); const alreadyInitialized = /* @__PURE__ */ new Set(); const addParentDomains = (parent) => { if (!parent?.stores) return; for (const domain of Object.values(parent.stores)) { if (domain) alreadyInitialized.add(domain); } }; addParentDomains(this.parentDefault); addParentDomains(this.parentEditor); const initTasks = []; const maybeInit = (domain) => { if (!domain || alreadyInitialized.has(domain)) return; initTasks.push(domain.init()); alreadyInitialized.add(domain); }; if (this.stores) { maybeInit(this.stores.memory); maybeInit(this.stores.workflows); maybeInit(this.stores.scores); maybeInit(this.stores.observability); maybeInit(this.stores.agents); maybeInit(this.stores.datasets); maybeInit(this.stores.experiments); maybeInit(this.stores.promptBlocks); maybeInit(this.stores.scorerDefinitions); maybeInit(this.stores.mcpClients); maybeInit(this.stores.mcpServers); maybeInit(this.stores.workspaces); maybeInit(this.stores.skills); maybeInit(this.stores.favorites); maybeInit(this.stores.blobs); maybeInit(this.stores.backgroundTasks); maybeInit(this.stores.schedules); maybeInit(this.stores.channels); } await Promise.all(initTasks); return true; } }; var MastraStorage = class extends MastraCompositeStore { }; // src/storage/domains/base.ts var StorageDomain = class extends chunkFCQNDFEW_cjs.MastraBase { /** * Initialize the storage domain. * This should create any necessary tables/collections. * Default implementation is a no-op - override in adapters that need initialization. */ async init() { } }; // src/storage/domains/versioned.ts var ENTITY_ORDER_BY_SET = { createdAt: true, updatedAt: true }; var SORT_DIRECTION_SET = { ASC: true, DESC: true }; var VERSION_ORDER_BY_SET = { versionNumber: true, createdAt: true }; var VersionedStorageDomain = class extends StorageDomain { // ========================================================================== // Concrete resolution methods // ========================================================================== /** * Strips version metadata fields from a version row, leaving only snapshot config fields. */ extractSnapshotConfig(version) { const result = {}; const metadataSet = new Set(this.versionMetadataFields); for (const [key, value] of Object.entries(version)) { if (!metadataSet.has(key)) { result[key] = value; } } return result; } /** * Resolves an entity by merging its thin record with the active or latest version config. * - `{ status: 'draft' }` — resolve with the latest version. * - `{ status: 'published' }` (default) — resolve with the active version, falling back to latest. * - `{ versionId: '...' }` — resolve with a specific version by ID. */ async getByIdResolved(id, options) { const entity = await this.getById(id); if (!entity) { return null; } return this.resolveEntity(entity, options); } /** * Lists entities with version resolution. * When `status` is `'draft'`, each entity is resolved with its latest version. * When `status` is `'published'` (default), each entity is resolved with its active version. */ async listResolved(args) { const result = await this.list(args); const status = args?.status; const entities = result[this.listKey]; const resolved = await Promise.all( entities.map((entity) => this.resolveEntity(entity, { status })) ); return { ...result, [this.listKey]: resolved }; } /** * Resolves a single entity by merging it with its active or latest version. * - `{ versionId: '...' }` — resolve with a specific version by ID. * - `{ status: 'published' }` (default) — use activeVersionId, fall back to latest. * - `{ status: 'draft' }` — always use the latest version. */ async resolveEntity(entity, options) { const status = options?.status || "published"; let version = null; if (options?.versionId) { version = await this.getVersion(options.versionId); } else if (status === "draft") { version = await this.getLatestVersion(entity.id); } else { if (entity.activeVersionId) { version = await this.getVersion(entity.activeVersionId); if (!version) { this.logger?.warn?.( `Entity ${entity.id} has activeVersionId ${entity.activeVersionId} but version not found. Falling back to latest version.` ); } } if (!version) { version = await this.getLatestVersion(entity.id); } } if (version) { const snapshotConfig = this.extractSnapshotConfig(version); return { ...entity, ...snapshotConfig, resolvedVersionId: version.id }; } return entity; } // ========================================================================== // Protected Helper Methods // ========================================================================== parseOrderBy(orderBy, defaultDirection = "DESC") { return { field: orderBy?.field && orderBy.field in ENTITY_ORDER_BY_SET ? orderBy.field : "createdAt", direction: orderBy?.direction && orderBy.direction in SORT_DIRECTION_SET ? orderBy.direction : defaultDirection }; } parseVersionOrderBy(orderBy, defaultDirection = "DESC") { return { field: orderBy?.field && orderBy.field in VERSION_ORDER_BY_SET ? orderBy.field : "versionNumber", direction: orderBy?.direction && orderBy.direction in SORT_DIRECTION_SET ? orderBy.direction : defaultDirection }; } }; var GitHistory = class { /** Cache: dir → repo root (string) or `false` if not a repo. */ repoRootCache = /* @__PURE__ */ new Map(); /** Cache: `dir:filename:limit` → ordered commits (newest first). */ commitCache = /* @__PURE__ */ new Map(); /** Cache: `dir:commitHash:filename` → parsed JSON. */ snapshotCache = /* @__PURE__ */ new Map(); // =========================================================================== // Public API // =========================================================================== /** * Returns `true` if `dir` is inside a Git repository. * Result is cached after the first call per directory. */ async isGitRepo(dir) { const cached = this.repoRootCache.get(dir); if (cached === false) return false; if (typeof cached === "string") return true; try { const root = (await this.exec(dir, ["rev-parse", "--show-toplevel"])).trim(); this.repoRootCache.set(dir, root); return true; } catch { this.repoRootCache.set(dir, false); return false; } } /** * Get the list of commits that touched a specific file, newest first. * Returns an empty array if Git is unavailable or the file has no history. * * @param dir Absolute path to the storage directory * @param filename The JSON filename relative to `dir` (e.g., 'agents.json') * @param limit Maximum number of commits to retrieve */ async getFileHistory(dir, filename, limit = 50) { const cacheKey = `${dir}:${filename}:${limit}`; if (this.commitCache.has(cacheKey)) { return this.commitCache.get(cacheKey); } if (!await this.isGitRepo(dir)) { this.commitCache.set(cacheKey, []); return []; } try { const raw = await this.exec(dir, [ "log", `--max-count=${limit}`, "--format=%H|%aI|%aN|%s", "--follow", "--", filename ]); const commits = []; for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed) continue; const pipeIdx1 = trimmed.indexOf("|"); const pipeIdx2 = trimmed.indexOf("|", pipeIdx1 + 1); const pipeIdx3 = trimmed.indexOf("|", pipeIdx2 + 1); if (pipeIdx1 === -1 || pipeIdx2 === -1 || pipeIdx3 === -1) continue; commits.push({ hash: trimmed.slice(0, pipeIdx1), date: new Date(trimmed.slice(pipeIdx1 + 1, pipeIdx2)), author: trimmed.slice(pipeIdx2 + 1, pipeIdx3), message: trimmed.slice(pipeIdx3 + 1) }); } this.commitCache.set(cacheKey, commits); return commits; } catch { this.commitCache.set(cacheKey, []); return []; } } /** * Read and parse a JSON file at a specific Git commit. * Returns the parsed entity map, or `null` if the file didn't exist at that commit. * * @param dir Absolute path to the storage directory * @param commitHash Full or abbreviated commit SHA * @param filename The JSON filename relative to `dir` (e.g., 'agents.json') */ async getFileAtCommit(dir, commitHash, filename) { const cacheKey = `${dir}:${commitHash}:${filename}`; if (this.snapshotCache.has(cacheKey)) { return this.snapshotCache.get(cacheKey); } if (!await this.isGitRepo(dir)) return null; try { const relPath = this.relativeToRepo(dir, filename); const raw = await this.exec(dir, ["show", `${commitHash}:${relPath}`]); const parsed = JSON.parse(raw); this.snapshotCache.set(cacheKey, parsed); return parsed; } catch { return null; } } /** * Invalidate all caches. Call after external operations that change Git state * (e.g., the user commits or pulls). */ invalidateCache() { this.repoRootCache.clear(); this.commitCache.clear(); this.snapshotCache.clear(); } // =========================================================================== // Internals // =========================================================================== /** * Get the relative path from the Git repo root to a file in the storage directory. */ relativeToRepo(dir, filename) { const root = this.repoRootCache.get(dir); if (!root) { throw new Error(`Not a git repository: ${dir}`); } const realRoot = fs.realpathSync(root); const realDir = fs.realpathSync(dir); const relDir = path.relative(realRoot, realDir); return relDir ? `${relDir}/${filename}` : filename; } /** * Execute a git command and return stdout. */ exec(cwd, args) { return new Promise((resolve, reject) => { child_process.execFile("git", args, { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => { if (error) reject(error); else resolve(stdout); }); }); } }; // src/storage/filesystem-versioned.ts var GIT_VERSION_PREFIX = "git-"; var FilesystemVersionedHelpers = class _FilesystemVersionedHelpers { db; entitiesFile; parentIdField; name; versionMetadataFields; gitHistoryLimit; /** * In-memory entity records (thin metadata), keyed by entity ID. */ entities = /* @__PURE__ */ new Map(); /** * In-memory version records, keyed by version ID. * Includes both in-memory/hydrated versions and git-based versions (metadata only). */ versions = /* @__PURE__ */ new Map(); /** * Whether we've loaded from disk yet. */ hydrated = false; /** * Git history utility instance (shared across all helpers). */ static gitHistory = new GitHistory(); /** * Promise that resolves when git history has been loaded. * null means git history loading hasn't been triggered yet. */ gitHistoryPromise = null; /** * The highest version number from git history, per entity ID. * Used to assign version numbers to new in-memory versions that continue * after the git history. */ gitVersionCounts = /* @__PURE__ */ new Map(); constructor(config) { this.db = config.db; this.entitiesFile = config.entitiesFile; this.parentIdField = config.parentIdField; this.name = config.name; this.versionMetadataFields = config.versionMetadataFields; this.gitHistoryLimit = config.gitHistoryLimit ?? 50; } /** * Check if a version ID represents a git-based version. */ static isGitVersion(id) { return id.startsWith(GIT_VERSION_PREFIX); } /** * Hydrate in-memory state from the on-disk JSON file. * For each entry on disk, creates an in-memory entity (status: 'published') * and a synthetic version with the snapshot config. * * Also kicks off async git history loading in the background. * Version numbers for hydrated entities are assigned as 1 initially, * but will be reassigned after git history loads. */ hydrate() { if (this.hydrated) return; this.hydrated = true; const diskData = this.db.readDomain(this.entitiesFile); for (const [entityId, snapshotConfig] of Object.entries(diskData)) { if (!snapshotConfig || typeof snapshotConfig !== "object") continue; const versionId = `hydrated-${entityId}-v1`; const now = /* @__PURE__ */ new Date(); const entity = { id: entityId, status: "published", activeVersionId: versionId, createdAt: now, updatedAt: now }; this.entities.set(entityId, entity); const version = { id: versionId, [this.parentIdField]: entityId, versionNumber: 1, ...snapshotConfig, createdAt: now }; this.versions.set(versionId, version); } this.gitHistoryPromise = this.loadGitHistory(); } /** * Ensure git history has been loaded before proceeding. * Call this in version-related methods to ensure git versions are available. */ async ensureGitHistory() { this.hydrate(); if (this.gitHistoryPromise) { await this.gitHistoryPromise; } } /** * Load git commit history for the domain's JSON file. * Creates read-only version records (metadata + snapshot config) for each * commit where an entity existed. Reassigns version numbers for * hydrated (current disk) versions to sit on top of git history. */ async loadGitHistory() { const git = _FilesystemVersionedHelpers.gitHistory; const dir = this.db.dir; const isRepo = await git.isGitRepo(dir); if (!isRepo) return; const commits = await git.getFileHistory(dir, this.entitiesFile, this.gitHistoryLimit); if (commits.length === 0) return; const orderedCommits = [...commits].reverse(); const entityVersionCount = /* @__PURE__ */ new Map(); const previousSnapshots = /* @__PURE__ */ new Map(); for (let i = 0; i < orderedCommits.length; i++) { const commit = orderedCommits[i]; const fileContent = await git.getFileAtCommit( dir, commit.hash, this.entitiesFile ); if (!fileContent) continue; for (const [entityId, snapshotConfig] of Object.entries(fileContent)) { if (!snapshotConfig || typeof snapshotConfig !== "object") continue; const serialized = JSON.stringify(snapshotConfig); if (previousSnapshots.get(entityId) === serialized) continue; previousSnapshots.set(entityId, serialized); const count = (entityVersionCount.get(entityId) ?? 0) + 1; entityVersionCount.set(entityId, count); const versionId = `${GIT_VERSION_PREFIX}${commit.hash}-${entityId}`; if (this.versions.has(versionId)) continue; const version = { id: versionId, [this.parentIdField]: entityId, versionNumber: count, changeMessage: commit.message, ...snapshotConfig, createdAt: commit.date }; this.versions.set(versionId, version); } } this.gitVersionCounts = entityVersionCount; for (const [entityId, gitCount] of entityVersionCount) { const hydratedVersionId = `hydrated-${entityId}-v1`; const version = this.versions.get(hydratedVersionId); if (version) { version.versionNumber = gitCount + 1; } } } // ========================================================================== // Disk persistence — only published snapshot configs // ========================================================================== /** * Write the published snapshot config for an entity to disk. * Strips all entity metadata and version metadata fields, leaving only * the clean primitive configuration. */ persistToDisk() { const diskData = {}; for (const [entityId, entity] of this.entities) { if (entity.status !== "published" || !entity.activeVersionId) continue; const version = this.versions.get(entity.activeVersionId); if (!version) continue; const snapshotConfig = this.extractSnapshotConfig(version); diskData[entityId] = snapshotConfig; } this.db.writeDomain(this.entitiesFile, diskData); } /** * Extract the snapshot config from a version, stripping version metadata fields. */ extractSnapshotConfig(version) { const metadataSet = new Set(this.versionMetadataFields); const result = {}; for (const [key, value] of Object.entries(version)) { if (!metadataSet.has(key)) { result[key] = value; } } return result; } // ========================================================================== // Entity CRUD // ========================================================================== async getById(id) { this.hydrate(); return this.entities.has(id) ? structuredClone(this.entities.get(id)) : null; } async createEntity(id, entity) { this.hydrate(); if (this.entities.has(id)) { throw new Error(`${this.name}: entity with id ${id} already exists`); } this.entities.set(id, structuredClone(entity)); return structuredClone(entity); } async updateEntity(id, updates) { this.hydrate(); const existing = this.entities.get(id); if (!existing) { throw new Error(`${this.name}: entity with id ${id} not found`); } const updated = { ...existing }; for (const [key, value] of Object.entries(updates)) { if (key === "id") continue; if (value === void 0) continue; if (key === "metadata" && typeof value === "object" && value !== null) { updated["metadata"] = { ...updated["metadata"] ?? {}, ...value }; } else { updated[key] = value; } } updated["updatedAt"] = /* @__PURE__ */ new Date(); const updatedEntity = updated; this.entities.set(id, structuredClone(updatedEntity)); const wasPublished = existing.status === "published"; const isPublished = updatedEntity.status === "published" && updatedEntity.activeVersionId; if (isPublished || wasPublished && updates["status"] !== void 0) { this.persistToDisk(); } return structuredClone(updatedEntity); } async deleteEntity(id) { this.hydrate(); this.entities.delete(id); await this.deleteVersionsByParentId(id); this.persistToDisk(); } async listEntities(args) { this.hydrate(); const { page = 0, perPage: perPageInput, orderBy, filters, listKey } = args; const perPage = normalizePerPage(perPageInput, 100); if (page < 0) throw new Error("page must be >= 0"); let entities = Array.from(this.entities.values()); if (filters) { for (const [key, value] of Object.entries(filters)) { if (value === void 0) continue; if (key === "metadata" && typeof value === "object" && value !== null) { entities = entities.filter((e) => { const meta = e["metadata"]; if (!meta) return false; return Object.entries(value).every( ([k, v]) => JSON.stringify(meta[k]) === JSON.stringify(v) ); }); } else { entities = entities.filter((e) => e[key] === value); } } } const field = orderBy?.field ?? "createdAt"; const direction = orderBy?.direction ?? "DESC"; entities.sort((a, b) => { const aVal = new Date(a[field]).getTime(); const bVal = new Date(b[field]).getTime(); return direction === "ASC" ? aVal - bVal : bVal - aVal; }); const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage); return { [listKey]: entities.slice(offset, offset + perPage), total: entities.length, page, perPage: perPageForResponse, hasMore: offset + perPage < entities.length }; } // ========================================================================== // Version Methods (in-memory + git history) // ========================================================================== async createVersion(input) { await this.ensureGitHistory(); if (this.versions.has(input.id)) { throw new Error(`${this.name}: version with id ${input.id} already exists`); } const parentId = input[this.parentIdField]; for (const v of this.versions.values()) { if (v[this.parentIdField] === parentId && v.versionNumber === input.versionNumber) { throw new Error(`${this.name}: version number ${input.versionNumber} already exists for entity ${parentId}`); } } const version = { ...input, createdAt: /* @__PURE__ */ new Date() }; this.versions.set(input.id, structuredClone(version)); return structuredClone(version); } async getVersion(id) { await this.ensureGitHistory(); return this.versions.has(id) ? structuredClone(this.versions.get(id)) : null; } async getVersionByNumber(entityId, versionNumber) { await this.ensureGitHistory(); for (const v of this.versions.values()) { if (v[this.parentIdField] === entityId && v.versionNumber === versionNumber) { return structuredClone(v); } } return null; } async getLatestVersion(entityId) { await this.ensureGitHistory(); let latest = null; for (const v of this.versions.values()) { if (v[this.parentIdField] === entityId) { if (!latest || v.versionNumber > latest.versionNumber) { latest = v; } } } return latest ? structuredClone(latest) : null; } async listVersions(input, parentIdField) { await this.ensureGitHistory(); const { page = 0, perPage: perPageInput, orderBy } = input; const entityId = input[parentIdField]; const perPage = normalizePerPage(perPageInput, 20); if (page < 0) throw new Error("page must be >= 0"); let versions = Array.from(this.versions.values()).filter( (v) => v[this.parentIdField] === entityId ); const field = orderBy?.field ?? "versionNumber"; const direction = orderBy?.direction ?? "DESC"; versions.sort((a, b) => { const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a.versionNumber; const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b.versionNumber; return direction === "ASC" ? aVal - bVal : bVal - aVal; }); const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage); return { versions: versions.slice(offset, offset + perPage), total: versions.length, page, perPage: perPageForResponse, hasMore: offset + perPage < versions.length }; } async deleteVersion(id) { await this.ensureGitHistory(); if (_FilesystemVersionedHelpers.isGitVersion(id)) return; this.versions.delete(id); } async deleteVersionsByParentId(entityId) { await this.ensureGitHistory(); for (const [versionId, version] of this.versions) { if (version[this.parentIdField] === entityId) { if (_FilesystemVersionedHelpers.isGitVersion(versionId)) continue; this.versions.delete(versionId); } } } async countVersions(entityId) { await this.ensureGitHistory(); let count = 0; for (const v of this.versions.values()) { if (v[this.parentIdField] === entityId) { count++; } } return count; } async getNextVersionNumber(entityId) { await this.ensureGitHistory(); return this._getNextVersionNumber(entityId); } _getNextVersionNumber(entityId) { const gitCount = this.gitVersionCounts.get(entityId) ?? 0; let maxVersion = gitCount; for (const v of this.versions.values()) { if (v[this.parentIdField] === entityId) { maxVersion = Math.max(maxVersion, v.versionNumber); } } return maxVersion + 1; } async dangerouslyClearAll() { this.entities.clear(); this.versions.clear(); this.gitVersionCounts.clear(); this.gitHistoryPromise = null; this.hydrated = false; this.db.clearDomain(this.entitiesFile); } }; exports.EDITOR_DOMAINS = EDITOR_DOMAINS; exports.FilesystemVersionedHelpers = FilesystemVersionedHelpers; exports.GitHistory = GitHistory; exports.MastraCompositeStore = MastraCompositeStore; exports.MastraStorage = MastraStorage; exports.StorageDomain = StorageDomain; exports.VersionedStorageDomain = VersionedStorageDomain; exports.calculatePagination = calculatePagination; exports.normalizePerPage = normalizePerPage; //# sourceMappingURL=chunk-3VBF5IP7.cjs.map //# sourceMappingURL=chunk-3VBF5IP7.cjs.map