PR: new state management setup
PublicPR: new state management setup
FlowStateManager
We now have some indirection between the db and manipulating the state within it. Rather than writing changes directly to the WASM instance of Sqlite3, we first write it to a serializable, in-memory representation.
A potential plan for this, other than making the state flows a little more intuitive, is to enable more convenient iteration on the underlying persistence technology, now we can more easily translate the in memory representation to any new schema or DB technology choice.
This should now also offer a natural point to issue automated syncing requests to the backend whenever the user is logged in.
We now have some indirection between the db and manipulating the state within it. Rather than writing changes directly to the WASM instance of Sqlite3, we first write it to a serializable, in-memory representation.
A potential plan for this, other than making the state flows a little more intuitive, is to enable more convenient iteration on the underlying persistence technology, now we can more easily translate the in memory representation to any new schema or DB technology choice.
This should now also offer a natural point to issue automated syncing requests to the backend whenever the user is logged in.
1: import { FlowState, FlowListItem, MatchState } from './types';
2: import { getDatabase, saveDatabase } from '../db-init';
3: import { queryAsync, getAsync, runAsync } from '../db/dbAsync';
4:
=> 5: class FlowStateManager {
6: private activeFlowState: FlowState | null = null;
7: private flowsListCache: FlowListItem[] | null = null;
8:
9: // Read Operations
10: async loadFlowsList(filter?: { repoRoot?: string }): Promise<FlowListItem[]> {
Fetch and cache
Now we are fetching the minimal set of data needed to present the "active" state in the app - namely the list of "flow" titiles (for the sidepanel) an the active flows combined state to render the markdown preview.
Now we are fetching the minimal set of data needed to present the "active" state in the app - namely the list of "flow" titiles (for the sidepanel) an the active flows combined state to render the markdown preview.
5: class FlowStateManager {
6: private activeFlowState: FlowState | null = null;
7: private flowsListCache: FlowListItem[] | null = null;
8:
9: // Read Operations
=> 10: async loadFlowsList(filter?: { repoRoot?: string }): Promise<FlowListItem[]> {
11: const db = getDatabase();
12:
13: const params: any[] = [];
14: let whereClause = 'WHERE f.archived = 0';
15:
Step 3
29: `;
30: this.flowsListCache = await queryAsync(db, sql, params);
31: return this.flowsListCache;
32: }
33:
=> 34: async loadFlowState(flowId: string): Promise<FlowState | null> {
35: const db = getDatabase();
36:
37: const flow = await getAsync(db, `
38: SELECT id, name, description, git_repo_root, git_commit_sha, git_branch,
39: archived, created_at, updated_at
Easier non-destructive change management
Now that we have a simple in-memory representation of the current state, we can more easily manipulate it. Non-destructive changes can first be done in memory, and later written to the db.
In-memory Schema
246: private transformToMatchState(rows: any[]): MatchState[] {
247: return rows.map(r => ({
248: flow_match_id: r.id,
249: match_id: r.matches_id,
250: order_index: r.order_index,
251: content_kind: r.content_kind,
252: match: r.file_path ? {
253: line: r.line,
254: file_path: r.file_path,
255: repo_relative_file_path: r.repo_relative_file_path,
256: file_name: r.file_name,
257: line_no: r.line_no,
258: grep_meta: r.grep_meta,
259: git_repo_root: r.git_repo_root,
260: git_commit_sha: r.git_commit_sha,
261: git_branch: r.git_branch
262: } : undefined,
263: note: (r.note_name || r.note_text) ? {
264: name: r.note_name,
265: description: r.note_text
266: } : undefined,
267: step_content: (r.step_content_title || r.step_content_body || r.step_content_file_path) ? {
268: title: r.step_content_title,
269: body: r.step_content_body,
270: file_path: r.step_content_file_path
271: } : undefined
272: }));
273: }
Step 6
85: this.activeFlowState.flow.description = description;
86: }
87: this.activeFlowState.isDirty = true;
88: }
89:
=> 90: updateMatchNote(flowMatchId: string, name: string, description: string): void {
91: if (!this.activeFlowState) {
92: return;
93: }
94: const match = this.activeFlowState.matches.find(m => m.flow_match_id === flowMatchId);
95: if (match) {
Step 7
111: match.step_content.body = body;
112: this.activeFlowState.isDirty = true;
113: }
114: }
115:
=> 116: reorderMatch(flowMatchId: string, newIndex: number): void {
117: if (!this.activeFlowState) {
118: return;
119: }
120: const matches = this.activeFlowState.matches;
121: const currentIndex = matches.findIndex(m => m.flow_match_id === flowMatchId);
Opt in persistence
Explicitly write out changes to the db when needed.
Explicitly write out changes to the db when needed.
193: async persist(): Promise<void> {
194: if (!this.activeFlowState || !this.activeFlowState.isDirty) {
195: return;
196: }
197:
198: const db = getDatabase();
199: const { flow, matches } = this.activeFlowState;
200:
201: await runAsync(db, `
The only exception
FlowMatch changes are destructive, we need to handle them immediately so we can recompute the order of steps correctly.
FlowMatch changes are destructive, we need to handle them immediately so we can recompute the order of steps correctly.
145: matches.forEach((m, i) => m.order_index = i);
146: this.activeFlowState.isDirty = true;
147: }
148: }
149:
=> 150: async deleteFlowMatch(flowMatchId: string): Promise<void> {
151: console.log(`[FlowStateManager] Deleting flow match id: ${flowMatchId}`);
152: const db = getDatabase();
153:
154: // Get flows_id and order_index before deletion
155: const current = await getAsync(db,