feat(variables): multiplayer variables through sockets, persist server side#933
feat(variables): multiplayer variables through sockets, persist server side#933icecrasher321 merged 3 commits intostagingfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
There was a problem hiding this comment.
Greptile Summary
This PR implements a comprehensive multiplayer variables system for the workflow application, enabling real-time collaborative editing of workflow variables across multiple users. The changes transform the variables system from a local-only state management approach to a fully collaborative, server-side persisted architecture.
The key architectural changes include:
-
Socket-based Real-time Synchronization: Added WebSocket handlers (
setupVariablesHandlers) to manage variable operations (add, update, delete, duplicate) with immediate broadcasting to all connected users in the same workflow room. -
Server-side Persistence: Implemented database operations in
handleVariableOperationTxthat directly manipulate theworkflow.variablesJSON field, ensuring all variable changes are persisted before being broadcast to other clients. -
Collaborative Operation Queue: Extended the existing operation queue system with variable-specific debouncing (25ms timeout), cleanup mechanisms, and cancellation capabilities to handle rapid updates efficiently.
-
Unified Variable Management: Modified the variables store to support predetermined IDs (
providedIdparameters) for consistent state across clients while removing local persistence middleware in favor of socket-based synchronization. -
Component Integration: Updated the Variables React component to use collaborative functions (
collaborativeAddVariable,collaborativeUpdateVariable, etc.) instead of direct store mutations, ensuring all user interactions trigger socket events.
The implementation follows the established patterns used for collaborative blocks and subblocks, maintaining consistency with the existing multiplayer architecture. Variables are now loaded during workflow fetching and synchronized through the same socket infrastructure used for other workflow elements, providing a seamless collaborative experience where multiple users can create, edit, and manage variables simultaneously with instant visual feedback.
Confidence score: 3/5
- This PR implements complex multiplayer functionality with potential race conditions and data consistency issues
- Score reflects concerns about type safety in validation schemas, JSON field manipulation, and transaction handling
- Pay close attention to
socket-server/handlers/variables.tsandsocket-server/database/operations.tsfor data integrity validation
12 files reviewed, 11 comments
| return // Early return for position updates | ||
| } | ||
|
|
||
| if (target === 'variable' && ['add', 'remove', 'duplicate'].includes(operation)) { |
There was a problem hiding this comment.
style: Consider using a more maintainable approach like a Set or constant array for operation types to make future additions easier
| type: z.any(), | ||
| value: z.any(), |
There was a problem hiding this comment.
style: Using z.any() for type and value reduces type safety. Consider defining specific schemas for variable types.
Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)
| if (variables && typeof variables === 'object') { | ||
| useVariablesStore.setState((state) => ({ | ||
| variables: { | ||
| ...state.variables, | ||
| ...variables, | ||
| }, | ||
| })) | ||
| } |
There was a problem hiding this comment.
logic: Potential variable conflict - this spreads all variables from DB into the store without checking workflowId. Variables from different workflows could overwrite each other if they have the same ID.
| z.object({ | ||
| operation: z.literal('duplicate'), | ||
| target: z.literal('variable'), | ||
| payload: z.object({ | ||
| sourceVariableId: z.string(), | ||
| id: z.string(), | ||
| }), | ||
| timestamp: z.number(), | ||
| operationId: z.string().optional(), | ||
| }), |
There was a problem hiding this comment.
logic: Duplicate operation lacks name, type, value, and workflowId fields that may be needed for complete variable duplication.
| userName: session.userName, | ||
| metadata: { | ||
| workflowId, | ||
| operationId: crypto.randomUUID(), |
There was a problem hiding this comment.
logic: The operationId from the original client request should be used instead of generating a new UUID for proper operation tracking
| throw new Error(`Workflow ${workflowId} not found`) | ||
| } | ||
|
|
||
| const currentVariables = (workflowData[0].variables as Record<string, any>) || {} |
There was a problem hiding this comment.
style: Consider using a more specific type instead of Record<string, any> to improve type safety
Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)
| const operationsToCancel = state.operations.filter( | ||
| (op) => | ||
| (op.operation.target === 'variable' && op.operation.payload?.variableId === variableId) || | ||
| (op.operation.target === 'variable' && | ||
| op.operation.payload?.sourceVariableId === variableId) | ||
| ) |
There was a problem hiding this comment.
logic: The filter logic uses both variableId and sourceVariableId but only variableId is used in the debounce key generation above. Ensure consistency in how variable operations are identified.
| const collaborativeAddVariable = useCallback( | ||
| (variableData: { name: string; type: any; value: any; workflowId: string }) => { | ||
| const id = crypto.randomUUID() | ||
| variablesStore.addVariable(variableData, id) |
There was a problem hiding this comment.
logic: Local variable addition before queuing operation could create inconsistencies if the queue operation fails
| return | ||
| } | ||
|
|
||
| const variables = (workflowRecord.variables as any) || {} |
There was a problem hiding this comment.
style: Using any type assertion loses type safety. Consider defining proper types for the variables structure.
Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)
| const roomManager = | ||
| deps instanceof Object && 'roomManager' in deps ? deps.roomManager : (deps as RoomManager) |
There was a problem hiding this comment.
style: Type guard logic is overly complex. Consider extracting this pattern into a utility function since it's repeated across handlers.
| get().processNextOperation() | ||
| }, | ||
|
|
||
| cancelOperationsForVariable: (variableId: string) => { |
There was a problem hiding this comment.
what does this do? Is this for debouncing?
…r side (simstudioai#933) * feat(variables): multiplayer variables through sockets, persist server side * remove extraneous comments * breakout variables handler in sockets
Summary
multiplayer variables through sockets, persist server side
Type of Change
Testing
Tested manually.
Checklist
Screenshots/Videos
Screen.Recording.2025-08-10.at.8.44.00.PM.mov