Remove work-related terminology by renaming "workspace" to "instance" across the entire application. This change better reflects that Memos is a self-hosted tool suitable for personal and non-work use cases. Breaking Changes: - API endpoints: /api/v1/workspace/* → /api/v1/instance/* - gRPC service: WorkspaceService → InstanceService - Proto types: WorkspaceSetting → InstanceSetting - Frontend translation keys: workspace-section → instance-section Backend Changes: - Renamed proto definitions and regenerated code - Updated all store layer methods and database drivers - Renamed service implementations and API handlers - Updated cache from workspaceSettingCache to instanceSettingCache Frontend Changes: - Renamed service client: workspaceServiceClient → instanceServiceClient - Updated all React components and state management - Refactored stores: workspace.ts → instance.ts - Updated all 32 locale translation files All tests pass and both backend and frontend build successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| README.md | ||
| attachment.ts | ||
| base-store.ts | ||
| common.ts | ||
| config.ts | ||
| index.ts | ||
| instance.ts | ||
| memo.ts | ||
| memoFilter.ts | ||
| store-utils.ts | ||
| user.ts | ||
| view.ts | ||
README.md
Store Architecture
This directory contains the application's state management implementation using MobX.
Overview
The store architecture follows a clear separation of concerns:
- Server State Stores: Manage data fetched from the backend API
- Client State Stores: Manage UI preferences and transient state
Store Files
Server State Stores (API Data)
| Store | File | Purpose |
|---|---|---|
memoStore |
memo.ts |
Memo CRUD operations, optimistic updates |
userStore |
user.ts |
User authentication, settings, stats |
instanceStore |
instance.ts |
Instance profile and settings |
attachmentStore |
attachment.ts |
File attachment management |
Features:
- ✅ Request deduplication (prevents duplicate API calls)
- ✅ Structured error handling with
StoreError - ✅ Computed property memoization for performance
- ✅ Optimistic updates (immediate UI feedback)
- ✅ Automatic caching
Client State Stores (UI State)
| Store | File | Purpose | Persistence |
|---|---|---|---|
viewStore |
view.ts |
Display preferences (sort, layout) | localStorage |
memoFilterStore |
memoFilter.ts |
Active search filters | URL params |
Features:
- ✅ No API calls (instant updates)
- ✅ localStorage persistence (viewStore)
- ✅ URL synchronization (memoFilterStore - shareable links)
Utilities
| File | Purpose |
|---|---|
base-store.ts |
Base classes and factory functions |
store-utils.ts |
Request deduplication, error handling, optimistic updates |
config.ts |
MobX configuration |
common.ts |
Shared constants and utilities |
index.ts |
Centralized exports |
Usage Examples
Basic Store Usage
import { memoStore, userStore, viewStore } from "@/store";
import { observer } from "mobx-react-lite";
const MyComponent = observer(() => {
// Access state
const memos = memoStore.state.memos;
const currentUser = userStore.state.currentUser;
const sortOrder = viewStore.state.orderByTimeAsc;
// Call actions
const handleCreate = async () => {
await memoStore.createMemo({ content: "Hello" });
};
const toggleSort = () => {
viewStore.toggleSortOrder();
};
return <div>...</div>;
});
Server Store Pattern
// Fetch data with automatic deduplication
const memo = await memoStore.getOrFetchMemoByName("memos/123");
// Update with optimistic UI updates
await memoStore.updateMemo({ name: "memos/123", content: "Updated" }, ["content"]);
// Errors are wrapped in StoreError
try {
await memoStore.deleteMemo("memos/123");
} catch (error) {
if (error instanceof StoreError) {
console.error(error.code, error.message);
}
}
Client Store Pattern
// View preferences (persisted to localStorage)
viewStore.setLayout("MASONRY");
viewStore.toggleSortOrder();
// Filters (synced to URL)
memoFilterStore.addFilter({ factor: "tagSearch", value: "work" });
memoFilterStore.removeFiltersByFactor("tagSearch");
memoFilterStore.clearAllFilters();
Creating New Stores
Server State Store
import { StandardState, createServerStore } from "./base-store";
import { createRequestKey, StoreError } from "./store-utils";
class MyState extends StandardState {
dataMap: Record<string, Data> = {};
get items() {
return Object.values(this.dataMap);
}
}
const myStore = (() => {
const base = createServerStore(new MyState(), {
name: "myStore",
enableDeduplication: true,
});
const { state, executeRequest } = base;
const fetchItems = async () => {
return executeRequest(
createRequestKey("fetchItems"),
async () => {
const items = await api.fetchItems();
state.setPartial({ dataMap: items });
return items;
},
"FETCH_ITEMS_FAILED"
);
};
return { state, fetchItems };
})();
Client State Store
import { StandardState } from "./base-store";
class MyState extends StandardState {
preference: string = "default";
setPartial(partial: Partial<MyState>) {
Object.assign(this, partial);
// Optional: persist to localStorage
localStorage.setItem("my-preference", JSON.stringify(this));
}
}
const myStore = (() => {
const state = new MyState();
const setPreference = (value: string) => {
state.setPartial({ preference: value });
};
return { state, setPreference };
})();
Best Practices
✅ Do
- Use
observer()HOC for components that access store state - Call store actions from event handlers
- Use computed properties for derived state
- Handle errors from async store operations
- Keep stores focused on a single domain
❌ Don't
- Don't mutate store state directly - use
setPartial()or action methods - Don't call async store methods during render
- Don't mix server and client state in the same store
- Don't access stores outside of React components (except initialization)
Performance Tips
- Computed Properties: Use getters for derived state - they're memoized by MobX
- Request Deduplication: Automatic for server stores - prevents wasted API calls
- Optimistic Updates: Used in
updateMemo- immediate UI feedback - Fine-grained Reactivity: MobX only re-renders components that access changed properties
Testing
import { memoStore } from "@/store";
describe("memoStore", () => {
it("should fetch memos", async () => {
const memos = await memoStore.fetchMemos({ filter: "..." });
expect(memos).toBeDefined();
});
it("should cache memos", () => {
const memo = memoStore.getMemoByName("memos/123");
expect(memo).toBeDefined();
});
});
Migration Guide
If you're migrating from old store patterns:
-
Replace direct state mutations with
setPartial():// Before store.state.value = 5; // After store.state.setPartial({ value: 5 }); -
Wrap API calls with
executeRequest():// Before const data = await api.fetch(); state.data = data; // After return executeRequest("fetchData", async () => { const data = await api.fetch(); state.setPartial({ data }); return data; }, "FETCH_FAILED"); -
Use StandardState for new stores:
// Before class State { constructor() { makeAutoObservable(this); } } // After class State extends StandardState { // makeAutoObservable() called automatically }
Troubleshooting
Q: Component not re-rendering when state changes?
A: Make sure you wrapped it with observer() from mobx-react-lite.
Q: Getting "Cannot modify state outside of actions" error?
A: Use state.setPartial() instead of direct mutations.
Q: API calls firing multiple times?
A: Check that your store uses createServerStore() with deduplication enabled.
Q: localStorage not persisting?
A: Ensure your client store overrides setPartial() to call localStorage.setItem().