feat: Implement Universal Graph Architecture - eliminate spoke tables (Issue #783)#786
Conversation
8166d73 to
91aeee7
Compare
… (Issue #783 Stream B) ## Summary This is the core data model transition from Hub-and-Spoke to Universal Graph Architecture. All node data (including schemas) now stored in single `node` table with `properties` JSON field. ## Changes ### Database Schema (`schema.surql`) - Removed `data` field (record link to spoke tables) - Added `properties` field for all type-specific data - Removed `schema` spoke table definition - All node types now use node.properties for storage ### SurrealStore - Removed all spoke table fetching logic (N+1 pattern eliminated) - `get_node()` now single query + memberships (was 3 queries) - `get_nodes_by_ids()` now single batch query + memberships - Removed `has_spoke_table()`, `batch_fetch_properties()` methods - Removed `types_with_spoke_tables` cache - Updated schema node methods to query from `node` table - Updated `create_schema_node_atomic()` to store in node.properties - Updated `get_task_node()` / `update_task_node()` for Universal Graph ### QueryService - Removed all spoke-centric query methods (`build_spoke_query()`, etc.) - Unified to single `build_query()` targeting node table - Property filters now use `properties.fieldName` pattern - Removed FETCH clause - no longer needed ### SchemaTableManager - Simplified to pure DDL generator (stateless) - Removed async database execution methods - Removed `sync_schema_to_database()`, `define_field()`, `create_field_index()` - Kept only DDL generation for indexes and edge tables ### Strongly-Typed Node Models - Updated `TaskNode` docs to reflect Universal Graph Architecture - Updated `SchemaNode` docs to reflect Universal Graph Architecture - Renamed `has_spoke_fields()` -> `has_property_fields()` - Renamed `has_hub_fields()` -> `has_content_field()` ## Impact - **~1,300 lines of code deleted** (1,705 deletions - 417 insertions) - **Query performance**: Single table queries, no N+1 spoke fetching - **Simpler sync**: Single record atomic operations - **Zero DDL for new types**: Custom types work via properties ## Test Results - Frontend: 3431 passed (no change from baseline) - Rust: 674 passed (no change from baseline) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
91aeee7 to
18bcf23
Compare
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Code Review SummaryPR #786: feat: Implement Universal Graph Architecture - eliminate spoke tables (Issue #783) Overall Assessment: This is an excellent architectural change that significantly simplifies the codebase by eliminating the hub-and-spoke pattern in favor of a Universal Graph Architecture. The change removes ~1,792 lines of code while maintaining full functionality. The PR is a net positive for code health. Verdict: APPROVE with minor suggestions FindingsCritical IssuesNone identified. The implementation is sound and aligns with the issue requirements. Suggested Improvements[Improvement] Test Double Inconsistency File: `/packages/core/src/services/schema_table_manager.rs` (lines 731-734) The `TestSchemaTableManager` helper in the unit tests generates edge table DDL with a different pattern than production: ```rust // Test helper (line 732): This means the test `test_generate_edge_table_ddl_basic` (line 471-472) asserts: But the production code would generate: Principle: Test doubles should mirror production behavior to prevent false confidence. This test passes but validates incorrect behavior. Recommendation: Update `TestSchemaTableManager.generate_edge_table_ddl()` to match production: And update the test assertion accordingly. [Improvement] Consider extracting DDL constants File: `/packages/core/src/services/schema_table_manager.rs` The DDL template strings are duplicated between `SchemaTableManager` and `TestSchemaTableManager`. Consider extracting these as constants or using a shared method to ensure consistency. NitpicksNit: `/packages/core/src/db/surreal_store.rs` (line 601) Minor log statement change from emoji to plain text is inconsistent with other places in the codebase that use emojis: This is minor and could be addressed in a follow-up cleanup pass. Requirements ValidationFrom Issue #783 Stream B acceptance criteria:
All Stream B requirements are satisfied. Testing
SummaryThis is a well-executed architectural refactoring that delivers significant simplification:
The only actionable item is the test double inconsistency which should be corrected to ensure tests validate actual production behavior. Pragmatic Code Review by Claude Opus 4.5 |
malibio
left a comment
There was a problem hiding this comment.
Review Complete - Recommendation: APPROVE
This is an excellent architectural refactoring that eliminates the hub-and-spoke complexity in favor of Universal Graph Architecture. The change delivers a net reduction of ~1,800 lines of code while maintaining full functionality.
One improvement suggestion: Fix the test double inconsistency in TestSchemaTableManager to match production behavior (IN node OUT node vs IN {source_type} OUT {target_type}). This can be addressed in a follow-up commit.
All Stream B requirements from Issue #783 are satisfied. Ready to merge.
Address code review recommendation: TestSchemaTableManager now generates
edge table DDL matching production behavior using 'IN node OUT node'
instead of 'IN {source_type} OUT {target_type}'.
Universal Graph Architecture (Issue #783) stores all nodes in the 'node'
table, so relationship edge tables must reference 'node' for both IN and
OUT clauses, not the schema type names.
Changes:
- Update TestSchemaTableManager.generate_edge_table_ddl() to use 'node'
- Update test_generate_edge_table_ddl_basic assertion to expect 'node'
- Add explanatory comments referencing Issue #783
Skipped recommendations (engineering judgment):
- DDL constant extraction: Marginal benefit for test code duplication
- Emoji in log statements: Purely cosmetic, no functional impact
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Review Feedback Addressed✅ Addressed: 1 recommendationTest Double Inconsistency (🟡 Important)
Commit: aa2f7ea ⏭️ Skipped: 2 recommendations (engineering judgment)
Summary
Review feedback addressed by Claude Opus 4.5 |
… (Issue #783) (#786) * feat: Implement Universal Graph Architecture - eliminate spoke tables (Issue #783 Stream B) ## Summary This is the core data model transition from Hub-and-Spoke to Universal Graph Architecture. All node data (including schemas) now stored in single `node` table with `properties` JSON field. ## Changes ### Database Schema (`schema.surql`) - Removed `data` field (record link to spoke tables) - Added `properties` field for all type-specific data - Removed `schema` spoke table definition - All node types now use node.properties for storage ### SurrealStore - Removed all spoke table fetching logic (N+1 pattern eliminated) - `get_node()` now single query + memberships (was 3 queries) - `get_nodes_by_ids()` now single batch query + memberships - Removed `has_spoke_table()`, `batch_fetch_properties()` methods - Removed `types_with_spoke_tables` cache - Updated schema node methods to query from `node` table - Updated `create_schema_node_atomic()` to store in node.properties - Updated `get_task_node()` / `update_task_node()` for Universal Graph ### QueryService - Removed all spoke-centric query methods (`build_spoke_query()`, etc.) - Unified to single `build_query()` targeting node table - Property filters now use `properties.fieldName` pattern - Removed FETCH clause - no longer needed ### SchemaTableManager - Simplified to pure DDL generator (stateless) - Removed async database execution methods - Removed `sync_schema_to_database()`, `define_field()`, `create_field_index()` - Kept only DDL generation for indexes and edge tables ### Strongly-Typed Node Models - Updated `TaskNode` docs to reflect Universal Graph Architecture - Updated `SchemaNode` docs to reflect Universal Graph Architecture - Renamed `has_spoke_fields()` -> `has_property_fields()` - Renamed `has_hub_fields()` -> `has_content_field()` ## Impact - **~1,300 lines of code deleted** (1,705 deletions - 417 insertions) - **Query performance**: Single table queries, no N+1 spoke fetching - **Simpler sync**: Single record atomic operations - **Zero DDL for new types**: Custom types work via properties ## Test Results - Frontend: 3431 passed (no change from baseline) - Rust: 674 passed (no change from baseline) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Apply formatting and update baseline-browser-mapping 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Align test double with production DDL generation (review feedback) Address code review recommendation: TestSchemaTableManager now generates edge table DDL matching production behavior using 'IN node OUT node' instead of 'IN {source_type} OUT {target_type}'. Universal Graph Architecture (Issue #783) stores all nodes in the 'node' table, so relationship edge tables must reference 'node' for both IN and OUT clauses, not the schema type names. Changes: - Update TestSchemaTableManager.generate_edge_table_ddl() to use 'node' - Update test_generate_edge_table_ddl_basic assertion to expect 'node' - Add explanatory comments referencing Issue #783 Skipped recommendations (engineering judgment): - DDL constant extraction: Marginal benefit for test code duplication - Emoji in log statements: Purely cosmetic, no functional impact 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
… (Issue #783) (#786) * feat: Implement Universal Graph Architecture - eliminate spoke tables (Issue #783 Stream B) ## Summary This is the core data model transition from Hub-and-Spoke to Universal Graph Architecture. All node data (including schemas) now stored in single `node` table with `properties` JSON field. ## Changes ### Database Schema (`schema.surql`) - Removed `data` field (record link to spoke tables) - Added `properties` field for all type-specific data - Removed `schema` spoke table definition - All node types now use node.properties for storage ### SurrealStore - Removed all spoke table fetching logic (N+1 pattern eliminated) - `get_node()` now single query + memberships (was 3 queries) - `get_nodes_by_ids()` now single batch query + memberships - Removed `has_spoke_table()`, `batch_fetch_properties()` methods - Removed `types_with_spoke_tables` cache - Updated schema node methods to query from `node` table - Updated `create_schema_node_atomic()` to store in node.properties - Updated `get_task_node()` / `update_task_node()` for Universal Graph ### QueryService - Removed all spoke-centric query methods (`build_spoke_query()`, etc.) - Unified to single `build_query()` targeting node table - Property filters now use `properties.fieldName` pattern - Removed FETCH clause - no longer needed ### SchemaTableManager - Simplified to pure DDL generator (stateless) - Removed async database execution methods - Removed `sync_schema_to_database()`, `define_field()`, `create_field_index()` - Kept only DDL generation for indexes and edge tables ### Strongly-Typed Node Models - Updated `TaskNode` docs to reflect Universal Graph Architecture - Updated `SchemaNode` docs to reflect Universal Graph Architecture - Renamed `has_spoke_fields()` -> `has_property_fields()` - Renamed `has_hub_fields()` -> `has_content_field()` ## Impact - **~1,300 lines of code deleted** (1,705 deletions - 417 insertions) - **Query performance**: Single table queries, no N+1 spoke fetching - **Simpler sync**: Single record atomic operations - **Zero DDL for new types**: Custom types work via properties ## Test Results - Frontend: 3431 passed (no change from baseline) - Rust: 674 passed (no change from baseline) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Apply formatting and update baseline-browser-mapping 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Align test double with production DDL generation (review feedback) Address code review recommendation: TestSchemaTableManager now generates edge table DDL matching production behavior using 'IN node OUT node' instead of 'IN {source_type} OUT {target_type}'. Universal Graph Architecture (Issue #783) stores all nodes in the 'node' table, so relationship edge tables must reference 'node' for both IN and OUT clauses, not the schema type names. Changes: - Update TestSchemaTableManager.generate_edge_table_ddl() to use 'node' - Update test_generate_edge_table_ddl_basic assertion to expect 'node' - Add explanatory comments referencing Issue #783 Skipped recommendations (engineering judgment): - DDL constant extraction: Marginal benefit for test code duplication - Emoji in log statements: Purely cosmetic, no functional impact 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
… (Issue #783) (#786) * feat: Implement Universal Graph Architecture - eliminate spoke tables (Issue #783 Stream B) ## Summary This is the core data model transition from Hub-and-Spoke to Universal Graph Architecture. All node data (including schemas) now stored in single `node` table with `properties` JSON field. ## Changes ### Database Schema (`schema.surql`) - Removed `data` field (record link to spoke tables) - Added `properties` field for all type-specific data - Removed `schema` spoke table definition - All node types now use node.properties for storage ### SurrealStore - Removed all spoke table fetching logic (N+1 pattern eliminated) - `get_node()` now single query + memberships (was 3 queries) - `get_nodes_by_ids()` now single batch query + memberships - Removed `has_spoke_table()`, `batch_fetch_properties()` methods - Removed `types_with_spoke_tables` cache - Updated schema node methods to query from `node` table - Updated `create_schema_node_atomic()` to store in node.properties - Updated `get_task_node()` / `update_task_node()` for Universal Graph ### QueryService - Removed all spoke-centric query methods (`build_spoke_query()`, etc.) - Unified to single `build_query()` targeting node table - Property filters now use `properties.fieldName` pattern - Removed FETCH clause - no longer needed ### SchemaTableManager - Simplified to pure DDL generator (stateless) - Removed async database execution methods - Removed `sync_schema_to_database()`, `define_field()`, `create_field_index()` - Kept only DDL generation for indexes and edge tables ### Strongly-Typed Node Models - Updated `TaskNode` docs to reflect Universal Graph Architecture - Updated `SchemaNode` docs to reflect Universal Graph Architecture - Renamed `has_spoke_fields()` -> `has_property_fields()` - Renamed `has_hub_fields()` -> `has_content_field()` ## Impact - **~1,300 lines of code deleted** (1,705 deletions - 417 insertions) - **Query performance**: Single table queries, no N+1 spoke fetching - **Simpler sync**: Single record atomic operations - **Zero DDL for new types**: Custom types work via properties ## Test Results - Frontend: 3431 passed (no change from baseline) - Rust: 674 passed (no change from baseline) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Apply formatting and update baseline-browser-mapping 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Align test double with production DDL generation (review feedback) Address code review recommendation: TestSchemaTableManager now generates edge table DDL matching production behavior using 'IN node OUT node' instead of 'IN {source_type} OUT {target_type}'. Universal Graph Architecture (Issue #783) stores all nodes in the 'node' table, so relationship edge tables must reference 'node' for both IN and OUT clauses, not the schema type names. Changes: - Update TestSchemaTableManager.generate_edge_table_ddl() to use 'node' - Update test_generate_edge_table_ddl_basic assertion to expect 'node' - Add explanatory comments referencing Issue #783 Skipped recommendations (engineering judgment): - DDL constant extraction: Marginal benefit for test code duplication - Emoji in log statements: Purely cosmetic, no functional impact 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Implements Stream B of Issue #783: Universal Graph Architecture Transition. This eliminates all spoke tables (task, schema) and stores all node properties in the
node.propertiesJSON field.Key changes:
surreal_store.rs(~1,000 lines removed)get_subtree_with_edgesto single query batch againstnodetableSchemaTableManagerto only generate DDL for relationship edge tablesNodeServiceschemaVersioninstead ofversion)Net result: -1,792 lines of code (2,535 deletions, 743 additions)
Changes by File
surreal_store.rsnode_service.rsschema_table_manager.rsgenerate_ddl_statements, kept only relationship DDLschema_table_manager_test.rsquery_service/mod.rsschema.surqlschema_node.rs,task_node.rsTest Plan
~/.nodespace/database/nodespace-devdeleted)Architecture
Related Issues
🤖 Generated with Claude Code