UNPKG

@mastra/core

Version:
1 lines 11 kB
{"version":3,"file":"inmemory-D6UzQ68e.cjs","names":["MastraBase"],"sources":["../src/storage/domains/favorites/base.ts","../src/storage/domains/favorites/inmemory.ts"],"sourcesContent":["import { MastraBase } from '../../../base';\nimport type {\n StorageDeleteFavoritesForEntityInput,\n StorageIsFavoritedBatchInput,\n StorageListFavoritesInput,\n StorageFavoriteEntityType,\n StorageFavoriteKey,\n} from '../../types';\n\n/**\n * Result of a favorite/unfavorite operation. `favorited` reflects the new state\n * for the caller; `favoriteCount` reflects the entity's denormalized counter\n * after the operation.\n */\nexport interface FavoriteToggleResult {\n favorited: boolean;\n favoriteCount: number;\n}\n\n/**\n * Abstract base class for favorites storage.\n *\n * The favorites domain is responsible for:\n * - persisting `(userId, entityType, entityId)` favorite rows,\n * - maintaining the denormalized `favoriteCount` on the parent entity record,\n * - answering batched lookups for list-response annotation.\n *\n * EE feature gating is the server-handler concern, not the storage domain.\n */\nexport abstract class FavoritesStorage extends MastraBase {\n constructor() {\n super({\n component: 'STORAGE',\n name: 'FAVORITES',\n });\n }\n\n /**\n * Initialize the favorites store (create tables, indexes, etc).\n */\n abstract init(): Promise<void>;\n\n /**\n * Favorite an entity for a user. Idempotent — re-favoriting an already-favorited\n * entity is a no-op and returns the current state.\n *\n * Implementations must atomically insert the favorite row and increment the\n * entity's `favoriteCount`. If the entity does not exist, throw.\n */\n abstract favorite(input: StorageFavoriteKey): Promise<FavoriteToggleResult>;\n\n /**\n * Unfavorite an entity for a user. Idempotent — unfavoriting a non-favorited\n * entity is a no-op and returns the current state.\n *\n * Implementations must atomically delete the favorite row and decrement the\n * entity's `favoriteCount` (clamped at 0). If the entity does not exist,\n * throw.\n */\n abstract unfavorite(input: StorageFavoriteKey): Promise<FavoriteToggleResult>;\n\n /**\n * Check whether a single entity is favorited by the given user.\n */\n abstract isFavorited(input: StorageFavoriteKey): Promise<boolean>;\n\n /**\n * Look up which entity IDs in a candidate set are favorited by the given user.\n * Used to annotate list responses.\n *\n * Returns a Set of favorited entity IDs. Order does not matter.\n */\n abstract isFavoritedBatch(input: StorageIsFavoritedBatchInput): Promise<Set<string>>;\n\n /**\n * List all entity IDs of the given type favorited by the user.\n * Used internally by the `?favoritedOnly=true` query handler to pre-filter\n * the candidate set for the existing list path.\n */\n abstract listFavoritedIds(input: StorageListFavoritesInput): Promise<string[]>;\n\n /**\n * Remove all favorite rows referencing the given entity. Called by\n * hard-delete handlers. Decrements no counters (the entity is being\n * removed).\n *\n * Returns the number of favorite rows removed.\n */\n abstract deleteFavoritesForEntity(input: StorageDeleteFavoritesForEntityInput): Promise<number>;\n\n /**\n * Delete all favorites. Used for testing.\n */\n abstract dangerouslyClearAll(): Promise<void>;\n}\n\nexport type { StorageFavoriteEntityType };\n","import type {\n StorageDeleteFavoritesForEntityInput,\n StorageIsFavoritedBatchInput,\n StorageListFavoritesInput,\n StorageFavoriteEntityType,\n StorageFavoriteKey,\n StorageFavoriteType,\n} from '../../types';\nimport type { InMemoryDB } from '../inmemory-db';\nimport type { FavoriteToggleResult } from './base';\nimport { FavoritesStorage } from './base';\n\n/**\n * Build the composite key used by the in-memory favorites Map.\n */\nfunction favoriteKey(userId: string, entityType: StorageFavoriteEntityType, entityId: string): string {\n return `${userId}\\u0000${entityType}\\u0000${entityId}`;\n}\n\n/**\n * In-memory implementation of FavoritesStorage. Mutates the shared InMemoryDB\n * Maps for favorites and the parent entity records (agents, skills) so that the\n * denormalized `favoriteCount` stays in sync.\n *\n * Atomicity is provided by the JavaScript single-threaded event loop: each\n * favorite/unfavorite runs to completion within one synchronous block.\n */\nexport class InMemoryFavoritesStorage extends FavoritesStorage {\n private db: InMemoryDB;\n\n constructor({ db }: { db: InMemoryDB }) {\n super();\n this.db = db;\n }\n\n async init(): Promise<void> {\n // No-op for in-memory store.\n }\n\n async dangerouslyClearAll(): Promise<void> {\n this.db.favorites.clear();\n // Keep denormalized counters in sync with the cleared favorites map.\n for (const agent of this.db.agents.values()) {\n if (agent.favoriteCount) agent.favoriteCount = 0;\n }\n for (const skill of this.db.skills.values()) {\n if (skill.favoriteCount) skill.favoriteCount = 0;\n }\n }\n\n async favorite({ userId, entityType, entityId }: StorageFavoriteKey): Promise<FavoriteToggleResult> {\n const entity = this.requireEntity(entityType, entityId);\n const key = favoriteKey(userId, entityType, entityId);\n\n if (this.db.favorites.has(key)) {\n return { favorited: true, favoriteCount: entity.favoriteCount ?? 0 };\n }\n\n const row: StorageFavoriteType = {\n userId,\n entityType,\n entityId,\n createdAt: new Date(),\n };\n this.db.favorites.set(key, row);\n\n const nextCount = (entity.favoriteCount ?? 0) + 1;\n entity.favoriteCount = nextCount;\n entity.updatedAt = new Date();\n\n return { favorited: true, favoriteCount: nextCount };\n }\n\n async unfavorite({ userId, entityType, entityId }: StorageFavoriteKey): Promise<FavoriteToggleResult> {\n const entity = this.requireEntity(entityType, entityId);\n const key = favoriteKey(userId, entityType, entityId);\n\n if (!this.db.favorites.has(key)) {\n return { favorited: false, favoriteCount: entity.favoriteCount ?? 0 };\n }\n\n this.db.favorites.delete(key);\n\n const nextCount = Math.max(0, (entity.favoriteCount ?? 0) - 1);\n entity.favoriteCount = nextCount;\n entity.updatedAt = new Date();\n\n return { favorited: false, favoriteCount: nextCount };\n }\n\n async isFavorited({ userId, entityType, entityId }: StorageFavoriteKey): Promise<boolean> {\n return this.db.favorites.has(favoriteKey(userId, entityType, entityId));\n }\n\n async isFavoritedBatch({ userId, entityType, entityIds }: StorageIsFavoritedBatchInput): Promise<Set<string>> {\n const result = new Set<string>();\n for (const entityId of entityIds) {\n if (this.db.favorites.has(favoriteKey(userId, entityType, entityId))) {\n result.add(entityId);\n }\n }\n return result;\n }\n\n async listFavoritedIds({ userId, entityType }: StorageListFavoritesInput): Promise<string[]> {\n const ids: string[] = [];\n for (const row of this.db.favorites.values()) {\n if (row.userId === userId && row.entityType === entityType) {\n ids.push(row.entityId);\n }\n }\n return ids;\n }\n\n async deleteFavoritesForEntity({ entityType, entityId }: StorageDeleteFavoritesForEntityInput): Promise<number> {\n let removed = 0;\n for (const [key, row] of this.db.favorites) {\n if (row.entityType === entityType && row.entityId === entityId) {\n this.db.favorites.delete(key);\n removed++;\n }\n }\n // Zero the parent's denormalized counter if the record still exists. The\n // cascade caller in the server typically deletes the entity first, in\n // which case this is a no-op — but callers that prune favorites for a still\n // existing entity (e.g. admin reset) need consistent counts.\n const map = entityType === 'agent' ? this.db.agents : this.db.skills;\n const entity = map.get(entityId);\n if (entity && entity.favoriteCount) {\n entity.favoriteCount = 0;\n }\n return removed;\n }\n\n /**\n * Look up the parent entity record for counter maintenance. Throws if the\n * entity does not exist — callers should validate existence (and access)\n * before invoking favorite/unfavorite.\n */\n private requireEntity(\n entityType: StorageFavoriteEntityType,\n entityId: string,\n ): { favoriteCount?: number; updatedAt: Date } {\n const map = entityType === 'agent' ? this.db.agents : this.db.skills;\n const entity = map.get(entityId);\n if (!entity) {\n throw new Error(`Cannot favorite: ${entityType} with id ${entityId} does not exist`);\n }\n return entity;\n }\n}\n"],"mappings":"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"}