Skip to content

Commit a9ea81e

Browse files
committed
ef
1 parent 68b9e53 commit a9ea81e

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Workspace State Persistence
2+
3+
This implementation adds automatic persistence of workspace state (open tabs, active tab, etc.) to UserData storage when tabs are opened, closed, or modified.
4+
5+
## How it works
6+
7+
### Automatic Saving
8+
9+
- **Redux Middleware**: A custom middleware (`workspacesStateChangeMiddleware`) detects whenever the workspaces state changes
10+
- **UserData Storage**: The state is automatically saved to persistent storage using MongoDB Compass's UserData system
11+
- **File Format**: Uses EJSON serialization with Zod schema validation
12+
- **Storage Location**: Saved under the key 'current-workspace' in WorkspacesState folder
13+
14+
### What gets saved
15+
16+
- All open tabs with their configuration:
17+
- Tab ID and type (Welcome, Shell, Collection, etc.)
18+
- Connection ID and namespace (for database/collection tabs)
19+
- Initial queries, aggregations, pipelines
20+
- Collection subtab selection (Documents, Schema, Indexes, etc.)
21+
- Shell evaluation state
22+
- Active tab ID
23+
- Timestamp of when state was saved
24+
25+
### State Restoration
26+
27+
- **Optional**: State restoration is provided via `configureStoreWithStateRestoration()` function
28+
- **Fallback**: If restoration fails or no saved state exists, falls back to provided initial tabs
29+
- **Logging**: Errors during restoration are logged for debugging
30+
31+
## Usage
32+
33+
### Basic (Existing behavior)
34+
35+
```typescript
36+
// This continues to work as before - no automatic restoration
37+
const store = configureStore(initialWorkspaceTabs, services);
38+
```
39+
40+
### With Optional State Restoration
41+
42+
```typescript
43+
// This will attempt to restore from UserData if no initial tabs provided
44+
const store = await configureStoreWithStateRestoration(
45+
initialWorkspaceTabs,
46+
services,
47+
true // enable restoration
48+
);
49+
```
50+
51+
### Manual State Loading
52+
53+
```typescript
54+
import { loadWorkspaceStateFromUserData } from './stores/workspaces-middleware';
55+
56+
const savedState = await loadWorkspaceStateFromUserData();
57+
if (savedState) {
58+
console.log('Found saved workspace state:', savedState);
59+
}
60+
```
61+
62+
## Files Added/Modified
63+
64+
### New Files
65+
66+
- `workspaces-storage.ts` - Zod schemas for UserData validation
67+
- `workspaces-middleware.ts` - Redux middleware and storage functions
68+
69+
### Modified Files
70+
71+
- `index.ts` - Added new store configuration function with restoration
72+
- Existing store configuration remains unchanged for backward compatibility
73+
74+
## Error Handling
75+
76+
- **Non-blocking**: Errors during saving/loading won't break the application
77+
- **Graceful degradation**: If restoration fails, app continues with provided initial tabs
78+
- **Console logging**: Errors are logged for debugging (only in development for saves)
79+
80+
## Storage Schema
81+
82+
The saved state follows this structure:
83+
84+
```typescript
85+
{
86+
tabs: Array<{
87+
id: string;
88+
type: "Welcome" | "My Queries" | "Shell" | "Collection" | ...;
89+
connectionId?: string;
90+
namespace?: string;
91+
initialQuery?: Record<string, any>;
92+
// ... other optional fields
93+
}>;
94+
activeTabId: string | null;
95+
timestamp: number;
96+
}
97+
```
98+
99+
## Development Notes
100+
101+
- The middleware runs on every Redux state change, but only saves if the state actually changed
102+
- Saving is async and non-blocking (fire-and-forget)
103+
- The schema allows for optional fields, gracefully handling different workspace types
104+
- Type conversion ensures compatibility between runtime types and storage schema
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { z } from '@mongodb-js/compass-user-data';
2+
3+
/**
4+
* Schema for saving workspace tab state to user data
5+
*/
6+
7+
// Define schema for collection subtab
8+
const CollectionSubtabSchema = z.enum([
9+
'Documents',
10+
'Aggregations',
11+
'Schema',
12+
'Indexes',
13+
'Validation',
14+
'GlobalWrites',
15+
]);
16+
17+
// Define schema for workspace tab type
18+
const WorkspaceTabTypeSchema = z.enum([
19+
'Welcome',
20+
'My Queries',
21+
'Data Modeling',
22+
'Shell',
23+
'Databases',
24+
'Performance',
25+
'Collections',
26+
'Collection',
27+
]);
28+
29+
// Define schema for a workspace tab
30+
export const WorkspaceTabSchema = z.object({
31+
id: z.string(),
32+
type: WorkspaceTabTypeSchema,
33+
connectionId: z.string().optional(),
34+
namespace: z.string().optional(),
35+
initialQuery: z.record(z.any()).optional(),
36+
initialAggregation: z.record(z.any()).optional(),
37+
initialPipeline: z.array(z.record(z.any())).optional(),
38+
initialPipelineText: z.string().optional(),
39+
editViewName: z.string().optional(),
40+
initialEvaluate: z.union([z.string(), z.array(z.string())]).optional(),
41+
initialInput: z.string().optional(),
42+
subTab: CollectionSubtabSchema.optional(),
43+
});
44+
45+
// Define schema for the complete workspaces state
46+
export const WorkspacesStateSchema = z.object({
47+
tabs: z.array(WorkspaceTabSchema),
48+
activeTabId: z.string().nullable(),
49+
timestamp: z.number(),
50+
});
51+
52+
// TypeScript types derived from the schemas
53+
export type WorkspaceTabData = z.infer<typeof WorkspaceTabSchema>;
54+
export type WorkspacesStateData = z.infer<typeof WorkspacesStateSchema>;

0 commit comments

Comments
 (0)