Skip to content

refactor(plugin-sql): extract domain stores from BaseDrizzleAdapter#6366

Merged
0xbbjoker merged 7 commits intodevelopfrom
refactor/plugin-sql-domain-stores
Jan 16, 2026
Merged

refactor(plugin-sql): extract domain stores from BaseDrizzleAdapter#6366
0xbbjoker merged 7 commits intodevelopfrom
refactor/plugin-sql-domain-stores

Conversation

@standujar
Copy link
Collaborator

@standujar standujar commented Jan 14, 2026

Refactors BaseDrizzleAdapter (~3,900 lines) into composable domain stores. This improves maintainability, testability, and separation of concerns without changing the public API.

Changes

New Domain Stores (src/stores/)

Store Responsibility Methods
AgentStore Agent CRUD operations get, getAll, create, update, delete, count
MemoryStore Memory & embeddings get, getById, getByIds, create, update, delete, search, count
RoomStore Room management getByIds, getByWorld, create, update, delete
ParticipantStore Room participants getRoomsForEntity, add, remove, getForRoom, isParticipant, userState
EntityStore Entity management getByIds, getForRoom, create, update, delete, getByNames, searchByName
ComponentStore ECS components get, getAll, create, update, delete
RelationshipStore Entity relationships create, update, get, getAll
CacheStore Key-value cache get, set, delete
WorldStore World management create, get, getAll, update, delete
TaskStore Task management create, getByParams, getByName, get, update, delete
LogStore Logging & summaries create, getMany, getAgentRunSummaries, delete
MessagingStore Servers, channels, messages createServer, getServers, createChannel, getChannels, createMessage, getMessages, participants

Architecture

BaseDrizzleAdapter
    │
    ├── initStores()  ───────────────────────────────┐
    │                                                │
    │   ┌─────────────────────────────────────────┐  │
    │   │           StoreContext                  │◄─┘
    │   │  • getDb()                              │
    │   │  • withRetry()                          │
    │   │  • withIsolationContext()               │
    │   │  • agentId                              │
    │   │  • getEmbeddingDimension()              │
    │   └─────────────────────────────────────────┘
    │                   │
    │         ┌─────────┴─────────┐
    │         ▼                   ▼
    │   ┌──────────┐       ┌──────────┐
    │   │  Store1  │  ...  │  StoreN  │
    │   └──────────┘       └──────────┘
    │
    └── Public methods delegate to stores

Key Design Decisions

  1. Shared Context: All stores receive a StoreContext with database access, retry logic, and RLS isolation

  2. No API Changes: All public methods remain identical - only internal implementation refactored

Impact

  • Lines reduced: base.ts from ~3,900 to ~1,350 lines (~65% reduction)
  • Files added: 13 new files in src/stores/
  • Public API: No changes
  • Tests: All 160 unit + all integration tests pass (PGLite + PostgreSQL)

Testing

# Unit tests
bun run test:unit        # 160 pass

# Integration tests (PGLite)
bun run test:integration # All pass

# Integration tests (PostgreSQL)
bun run test:integration:postgres # All pass (including RLS tests)

Migration

No migration needed - this is a pure refactoring with no public API changes.

Greptile Summary

Refactors BaseDrizzleAdapter from a monolithic ~3,900-line class into 12 composable domain stores, reducing the base adapter to ~1,350 lines while maintaining identical public API.

Key Changes:

  • Created StoreContext interface for dependency injection, providing stores with database access, retry logic, RLS isolation, and agent context
  • Extracted 12 domain stores: AgentStore, MemoryStore, RoomStore, ParticipantStore, EntityStore, ComponentStore, RelationshipStore, CacheStore, WorldStore, TaskStore, LogStore, MessagingStore
  • All public methods in BaseDrizzleAdapter now delegate to appropriate stores
  • Added initStores() calls in all three adapter implementations (PG, PGLite, Neon)
  • Improved testability by isolating domain logic into single-responsibility classes

Architecture:

  • Stores receive context at construction, don't manage connections
  • Public API preserved exactly - no breaking changes
  • All 160 unit tests and integration tests pass (PGLite + PostgreSQL including RLS tests)

Notable Implementation Details:

  • MemoryStore.getById splits queries to work around Drizzle leftJoin issue with dynamic vector columns (lines 140-164)
  • Entity and relationship stores use Drizzle's parameterized sql tagged templates for dynamic queries
  • Agent settings updates use deep merge logic to preserve nested configuration
  • Log store handles complex agent run summaries with JSON operators

Confidence Score: 5/5

  • This PR is safe to merge - it's a well-executed internal refactoring with no API changes and comprehensive test coverage
  • Pure refactoring with zero breaking changes. All 160+ tests pass across multiple database backends. Code is cleaner, more maintainable, and follows solid engineering principles. The delegation pattern is correctly implemented, stores are properly initialized, and the public API remains unchanged.
  • No files require special attention

Important Files Changed

Filename Overview
packages/plugin-sql/src/base.ts Refactored to delegate operations to domain stores, reducing from ~3,900 to ~1,350 lines. Public API unchanged, all methods properly delegate to stores.
packages/plugin-sql/src/stores/types.ts Defines StoreContext interface for dependency injection, providing clean abstraction for database access, retry logic, and RLS isolation.
packages/plugin-sql/src/stores/memory.store.ts Handles memory and embedding operations. Includes workaround for Drizzle leftJoin issue (line 140-164). Properly uses RLS isolation context.
packages/plugin-sql/src/stores/agent.store.ts Agent CRUD with deep merge logic for settings updates. Clean implementation with proper error handling.
packages/plugin-sql/src/stores/entity.store.ts Entity CRUD with SQL for dynamic name queries. Properly parameterized queries using Drizzle's sql tagged template.
packages/plugin-sql/src/stores/log.store.ts Logging with complex agent run summaries. Uses JSON operators for aggregation queries. Properly sanitizes JSON before insertion.
packages/plugin-sql/src/stores/messaging.store.ts Message server, channel, and message operations. Clean separation of concerns with proper transaction handling for multi-step operations.

Sequence Diagram

sequenceDiagram
    participant Client
    participant BaseDrizzleAdapter
    participant StoreContext
    participant MemoryStore
    participant EntityStore
    participant Database

    Note over BaseDrizzleAdapter: Constructor called
    BaseDrizzleAdapter->>BaseDrizzleAdapter: Set db instance
    BaseDrizzleAdapter->>BaseDrizzleAdapter: initStores()
    BaseDrizzleAdapter->>StoreContext: Create context with<br/>getDb(), withRetry(),<br/>withIsolationContext()
    BaseDrizzleAdapter->>MemoryStore: new MemoryStore(ctx)
    BaseDrizzleAdapter->>EntityStore: new EntityStore(ctx)
    Note over BaseDrizzleAdapter: All 12 stores initialized

    Client->>BaseDrizzleAdapter: createMemory(memory, tableName)
    BaseDrizzleAdapter->>BaseDrizzleAdapter: withDatabase(operation)
    BaseDrizzleAdapter->>MemoryStore: create(memory, tableName)
    MemoryStore->>StoreContext: withIsolationContext(entityId, callback)
    StoreContext->>BaseDrizzleAdapter: withIsolationContext(entityId, callback)
    BaseDrizzleAdapter->>Database: Execute with RLS context
    Database-->>BaseDrizzleAdapter: Result
    BaseDrizzleAdapter-->>StoreContext: Result
    StoreContext-->>MemoryStore: Result
    MemoryStore-->>BaseDrizzleAdapter: UUID
    BaseDrizzleAdapter-->>Client: UUID

    Client->>BaseDrizzleAdapter: getEntitiesByIds(entityIds)
    BaseDrizzleAdapter->>BaseDrizzleAdapter: withDatabase(operation)
    BaseDrizzleAdapter->>EntityStore: getByIds(entityIds)
    EntityStore->>StoreContext: withRetry(operation)
    StoreContext->>BaseDrizzleAdapter: withRetry(operation)
    BaseDrizzleAdapter->>Database: SELECT with JOIN
    Database-->>BaseDrizzleAdapter: Rows
    BaseDrizzleAdapter-->>StoreContext: Rows
    StoreContext-->>EntityStore: Rows
    EntityStore-->>BaseDrizzleAdapter: Entity[]
    BaseDrizzleAdapter-->>Client: Entity[]
Loading

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/plugin-sql-domain-stores

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

This comment was marked as outdated.

greptile-apps[bot]

This comment was marked as resolved.

@claude

This comment was marked as resolved.

@standujar
Copy link
Collaborator Author

standujar commented Jan 14, 2026

1. task.store.ts:39 - entityId unused in getAll
The entityId parameter matches the interface signature for future use - filtering can be done by callers if needed.

2. task.store.ts:142-146 - Missing agentId filter in delete
Will do that.

3. entity.store.ts:228 - SQL injection in searchByName
False positive - Drizzle's sql template tag properly parameterizes all interpolated values, preventing SQL injection.

4. entity.store.ts:185 - SQL injection in getByNames
False positive - Drizzle's sql template tag properly parameterizes the name values.

5. agent.store.ts:107 - Settings mutation
The merged settings are used immediately and the original object isn't retained, so no side effects occur in practice.

6. component.store.ts:40-41 - Empty string as UUID fallback
Matches existing codebase pattern where optional UUIDs use empty string to avoid null handling complexity.

7. log.store.ts:66-71 - JSON SQL interpolation
Safe - Drizzle's sql template tag properly escapes the JSON string parameter.

@claude

@claude

This comment was marked as resolved.

@standujar standujar marked this pull request as draft January 15, 2026 00:16
standujar and others added 4 commits January 15, 2026 14:33
Split the memory lookup and embedding fetch into separate queries to
work around a Drizzle ORM issue with leftJoin on dynamic vector columns
that causes "Object.entries requires that input parameter not be null"
errors.

Related Drizzle issues:
- drizzle-team/drizzle-orm#1603
- drizzle-team/drizzle-orm#2157
@standujar standujar marked this pull request as ready for review January 16, 2026 13:43
@claude
Copy link
Contributor

claude bot commented Jan 16, 2026

Claude finished @standujar's task in 1m 57s —— View job


Reviewing PR #6366: Extract domain stores from BaseDrizzleAdapter

  • Analyze PR changes and architecture
  • Review new domain stores for issues
  • Check test coverage
  • Provide final review feedback

Summary

This is a well-executed refactoring that extracts ~3,900 lines from BaseDrizzleAdapter into 12 composable domain stores. The architecture is clean and the public API remains unchanged.


✅ Verified Implementations

Store Lines Status
AgentStore 254 ✅ Clean implementation with retry logic
MemoryStore 497 ✅ Proper embedding handling
EntityStore 257 ✅ Component aggregation works correctly
TaskStore 149 Fixed: delete() now includes agentId filter (line 146)
LogStore 330 Fixed: delete() now uses withRetry (line 326)
RoomStore 103 ✅ Clean CRUD operations
ParticipantStore 219 ✅ Proper room management
ComponentStore 111 ✅ ECS component handling
RelationshipStore 143 ✅ Entity relationship management
CacheStore 97 ✅ Key-value cache
WorldStore 58 ✅ World management
MessagingStore 617 ✅ Server/channel/message handling

Architecture Quality ✅

Strengths:

  • Clean StoreContext pattern provides consistent database access, retry logic, and RLS isolation
  • All 3 adapters (PGLite, PostgreSQL, Neon) correctly call initStores() in constructors
  • Proper use of @elizaos/core imports throughout
  • TypeScript types are specific (no any types in store logic)
  • sanitizeJsonObject properly extracted to shared utils.ts

Security ✅

  • No SQL injection: Drizzle's sql template tag properly parameterizes all values (entity.store.ts lines 185, 228)
  • Agent ID filtering: Correctly applied in TaskStore methods (get, getAll, getByName, update, delete)
  • RLS isolation: withIsolationContext properly used for entity-scoped operations

Test Coverage ✅

  • 60+ test files covering integration, unit, and migration tests
  • Uses bun test exclusively (no jest/vitest)
  • Tests cover PGLite, PostgreSQL, and RLS scenarios

Minor Notes (Non-blocking)

💡 TaskStore.getAll accepts entityId parameter but doesn't use it (task.store.ts:39) - Author noted this is for interface compatibility

💡 Empty string UUID fallbacks in ComponentStore (component.store.ts:40-41) - Author confirmed this matches existing codebase pattern


Verdict

✅ APPROVE

The refactoring is clean, well-tested, and maintains API compatibility. Previous review feedback has been addressed:

  • TaskStore.delete security fix ✓
  • LogStore.delete retry logic ✓
  • Utils extraction ✓

Copy link
Collaborator

@0xbbjoker 0xbbjoker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good PR. Approved.

@0xbbjoker 0xbbjoker merged commit bb7b706 into develop Jan 16, 2026
18 checks passed
@0xbbjoker 0xbbjoker deleted the refactor/plugin-sql-domain-stores branch January 16, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants