Skip to content

Commit eb0e23e

Browse files
authored
Incremental tinybase adoption (#832)
1 parent f9982a2 commit eb0e23e

File tree

21 files changed

+317
-289
lines changed

21 files changed

+317
-289
lines changed

apps/desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"sonner": "^1.7.4",
8181
"tailwind-merge": "^2.6.0",
8282
"tauri-plugin-sentry-api": "^0.4.1",
83+
"tinybase": "^6.1.1",
8384
"tippy.js": "^6.3.7",
8485
"zod": "^3.24.4",
8586
"zustand": "^5.0.4"

apps/desktop/src/components/editor-area/note-header/listen-button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default function ListenButton({ sessionId }: { sessionId: string }) {
4949
const isEnhancePending = useEnhancePendingState(sessionId);
5050
const nonEmptySession = useSession(
5151
sessionId,
52-
(s) => s.session.conversations.length > 0 || s.session.enhanced_memo_html,
52+
(s) => s.session.words.length > 0 || s.session.enhanced_memo_html,
5353
);
5454
const meetingEnded = isEnhancePending || nonEmptySession;
5555

apps/desktop/src/components/toolbar/buttons/new-note-button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function NewNoteButtonInNote() {
2020
!s.session.title
2121
&& !s.session.raw_memo_html
2222
&& !s.session.enhanced_memo_html
23-
&& !s.session.conversations.length);
23+
&& !s.session.words.length);
2424

2525
return <ActualButton disabled={disabled} />;
2626
}

apps/desktop/src/contexts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from "./new-note";
66
export * from "./right-panel";
77
export * from "./search";
88
export * from "./settings";
9+
export * from "./tinybase";
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { useEffect } from "react";
2+
3+
import { createMergeableStore, createRelationships } from "tinybase";
4+
import { createCustomPersister } from "tinybase/persisters";
5+
import { createBroadcastChannelSynchronizer } from "tinybase/synchronizers/synchronizer-broadcast-channel";
6+
import { Provider, useCreateMergeableStore, useCreateRelationships } from "tinybase/ui-react";
7+
8+
import {
9+
type Calendar,
10+
commands as dbCommands,
11+
type Event,
12+
type Human,
13+
type Organization,
14+
type Session,
15+
} from "@hypr/plugin-db";
16+
import { useHypr } from "./hypr";
17+
18+
export function TinyBaseProvider({
19+
children,
20+
}: {
21+
children: React.ReactNode;
22+
}) {
23+
const { userId } = useHypr();
24+
const store = useCreateMergeableStore(createMergeableStore);
25+
26+
const persister = createCustomPersister(
27+
store,
28+
async () => {
29+
const [sessions, humans, organizations, calendars, events] = await Promise.all([
30+
dbCommands.listSessions(null),
31+
dbCommands.listHumans(null),
32+
dbCommands.listOrganizations(null),
33+
dbCommands.listCalendars(userId),
34+
dbCommands.listEvents(null),
35+
]);
36+
37+
return [{
38+
sessions: sessions.reduce((acc, session) => {
39+
acc[session.id] = {
40+
id: session.id,
41+
title: session.title ?? "",
42+
created_at: session.created_at ?? "",
43+
visited_at: session.visited_at ?? "",
44+
user_id: session.user_id ?? "",
45+
calendar_event_id: session.calendar_event_id ?? "",
46+
raw_memo_html: session.raw_memo_html ?? "",
47+
enhanced_memo_html: session.enhanced_memo_html ?? "",
48+
words: session.words ?? [],
49+
} satisfies Session;
50+
return acc;
51+
}, {} as Record<string, Record<string, any>>),
52+
humans: humans.reduce((acc, human) => {
53+
acc[human.id] = {
54+
id: human.id,
55+
organization_id: human.organization_id ?? "",
56+
is_user: human.is_user,
57+
full_name: human.full_name ?? "",
58+
email: human.email ?? "",
59+
job_title: human.job_title ?? "",
60+
linkedin_username: human.linkedin_username ?? "",
61+
} satisfies Human;
62+
return acc;
63+
}, {} as Record<string, Record<string, any>>),
64+
65+
organizations: organizations.reduce((acc, organization) => {
66+
acc[organization.id] = {
67+
id: organization.id,
68+
name: organization.name ?? "",
69+
description: organization.description ?? "",
70+
} satisfies Organization;
71+
return acc;
72+
}, {} as Record<string, Record<string, any>>),
73+
74+
calendars: calendars.reduce((acc, calendar) => {
75+
acc[calendar.id] = calendar satisfies Calendar;
76+
return acc;
77+
}, {} as Record<string, Record<string, any>>),
78+
events: events.reduce((acc, event) => {
79+
acc[event.id] = event satisfies Event;
80+
return acc;
81+
}, {} as Record<string, Record<string, any>>),
82+
}, {}];
83+
},
84+
async (getContent) => {
85+
const [t] = getContent();
86+
87+
const tables = t as unknown as {
88+
sessions: Session[];
89+
humans: Human[];
90+
organizations: Organization[];
91+
calendars: Calendar[];
92+
events: Event[];
93+
session_participants: { session_id: string; human_id: string }[];
94+
};
95+
96+
await Promise.all([
97+
...Object.values(tables.sessions).map(dbCommands.upsertSession),
98+
...Object.values(tables.humans).map(dbCommands.upsertHuman),
99+
...Object.values(tables.organizations).map(dbCommands.upsertOrganization),
100+
...Object.values(tables.calendars).map(dbCommands.upsertCalendar),
101+
...Object.values(tables.session_participants).map(p =>
102+
dbCommands.sessionAddParticipant(p.session_id, p.human_id)
103+
),
104+
]);
105+
},
106+
(listener) => setInterval(listener, 1000),
107+
(interval) => clearInterval(interval),
108+
undefined,
109+
3,
110+
);
111+
112+
// useEffect(() => {
113+
// persister.startAutoPersisting();
114+
// return () => {
115+
// persister.startAutoPersisting().then(() => persister.destroy());
116+
// };
117+
// }, []);
118+
119+
const relationships = useCreateRelationships(store, (store) => {
120+
const relationships = createRelationships(store);
121+
122+
relationships.setRelationshipDefinition(
123+
"humanOrganization",
124+
"humans",
125+
"organizations",
126+
"organization_id",
127+
);
128+
129+
relationships.setRelationshipDefinition(
130+
"humanSessions",
131+
"sessions",
132+
"humans",
133+
"user_id",
134+
);
135+
136+
relationships.setRelationshipDefinition(
137+
"participantHuman",
138+
"session_participants",
139+
"humans",
140+
"human_id",
141+
);
142+
143+
relationships.setRelationshipDefinition(
144+
"participantSession",
145+
"session_participants",
146+
"sessions",
147+
"session_id",
148+
);
149+
150+
relationships.setRelationshipDefinition(
151+
"userCalendars",
152+
"calendars",
153+
"humans",
154+
"user_id",
155+
);
156+
157+
relationships.setRelationshipDefinition(
158+
"userEvents",
159+
"events",
160+
"humans",
161+
"user_id",
162+
);
163+
164+
relationships.setRelationshipDefinition(
165+
"calendarEvents",
166+
"events",
167+
"calendars",
168+
"calendar_id",
169+
);
170+
171+
relationships.setRelationshipDefinition(
172+
"sessionEvent",
173+
"sessions",
174+
"events",
175+
"calendar_event_id",
176+
);
177+
178+
return relationships;
179+
});
180+
181+
useEffect(() => {
182+
const sync = createBroadcastChannelSynchronizer(
183+
store,
184+
"hyprnote-tinybase-sync",
185+
);
186+
187+
sync.startSync();
188+
189+
return () => {
190+
sync.stopSync().then(() => sync?.destroy());
191+
};
192+
}, [store]);
193+
194+
return (
195+
<Provider store={store} relationships={relationships} persister={persister}>
196+
{children}
197+
</Provider>
198+
);
199+
}

apps/desktop/src/routes/app.new.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export const Route = createFileRoute("/app/new")({
3333
title: event?.name ?? "",
3434
raw_memo_html: "",
3535
enhanced_memo_html: null,
36-
conversations: [],
3736
words: [],
3837
});
3938
await dbCommands.sessionAddParticipant(sessionId, userId);
@@ -50,7 +49,6 @@ export const Route = createFileRoute("/app/new")({
5049
title: "",
5150
raw_memo_html: "",
5251
enhanced_memo_html: null,
53-
conversations: [],
5452
words: [],
5553
});
5654
await dbCommands.sessionAddParticipant(sessionId, userId);

apps/desktop/src/routes/app.tsx

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
RightPanelProvider,
1616
SearchProvider,
1717
SettingsProvider,
18+
TinyBaseProvider,
1819
useLeftSidebar,
1920
useRightPanel,
2021
} from "@/contexts";
@@ -41,43 +42,45 @@ function Component() {
4142
return (
4243
<>
4344
<HyprProvider>
44-
<SessionsProvider store={sessionsStore}>
45-
<OngoingSessionProvider store={ongoingSessionStore}>
46-
<LeftSidebarProvider>
47-
<RightPanelProvider>
48-
<AudioPermissions />
49-
<MainWindowStateEventSupport />
50-
<SettingsProvider>
51-
<NewNoteProvider>
52-
<SearchProvider>
53-
<EditModeProvider>
54-
<div className="relative flex h-screen w-screen overflow-hidden">
55-
<LeftSidebar />
56-
<div className="flex-1 flex h-screen w-screen flex-col overflow-hidden">
57-
<Toolbar />
58-
<div className="flex-1 relative overflow-hidden flex">
59-
<div className="flex-1 overflow-hidden">
60-
<Outlet />
45+
<TinyBaseProvider>
46+
<SessionsProvider store={sessionsStore}>
47+
<OngoingSessionProvider store={ongoingSessionStore}>
48+
<LeftSidebarProvider>
49+
<RightPanelProvider>
50+
<AudioPermissions />
51+
<MainWindowStateEventSupport />
52+
<SettingsProvider>
53+
<NewNoteProvider>
54+
<SearchProvider>
55+
<EditModeProvider>
56+
<div className="relative flex h-screen w-screen overflow-hidden">
57+
<LeftSidebar />
58+
<div className="flex-1 flex h-screen w-screen flex-col overflow-hidden">
59+
<Toolbar />
60+
<div className="flex-1 relative overflow-hidden flex">
61+
<div className="flex-1 overflow-hidden">
62+
<Outlet />
63+
</div>
64+
<RightPanel />
6165
</div>
62-
<RightPanel />
6366
</div>
6467
</div>
65-
</div>
66-
<LoginModal
67-
isOpen={isOnboardingNeeded}
68-
onClose={() => {
69-
commands.setOnboardingNeeded(false);
70-
router.invalidate();
71-
}}
72-
/>
73-
</EditModeProvider>
74-
</SearchProvider>
75-
</NewNoteProvider>
76-
</SettingsProvider>
77-
</RightPanelProvider>
78-
</LeftSidebarProvider>
79-
</OngoingSessionProvider>
80-
</SessionsProvider>
68+
<LoginModal
69+
isOpen={isOnboardingNeeded}
70+
onClose={() => {
71+
commands.setOnboardingNeeded(false);
72+
router.invalidate();
73+
}}
74+
/>
75+
</EditModeProvider>
76+
</SearchProvider>
77+
</NewNoteProvider>
78+
</SettingsProvider>
79+
</RightPanelProvider>
80+
</LeftSidebarProvider>
81+
</OngoingSessionProvider>
82+
</SessionsProvider>
83+
</TinyBaseProvider>
8184
</HyprProvider>
8285
{showNotifications && <Notifications />}
8386
</>

crates/db-user/src/sessions_ops.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl UserDatabase {
230230
":title": session.title.clone(),
231231
":raw_memo_html": session.raw_memo_html.clone(),
232232
":enhanced_memo_html": session.enhanced_memo_html.clone(),
233-
":conversations": serde_json::to_string(&session.conversations).unwrap(),
233+
":conversations": "[]",
234234
":words": serde_json::to_string(&session.words).unwrap(),
235235
},
236236
)

crates/db-user/src/sessions_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ user_common_derives! {
1212
pub title: String,
1313
pub raw_memo_html: String,
1414
pub enhanced_memo_html: Option<String>,
15+
#[specta(skip)]
16+
#[serde(skip)]
1517
pub conversations: Vec<()>,
1618
pub words: Vec<hypr_listener_interface::Word>
1719
}

plugins/db/build.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ const COMMANDS: &[&str] = &[
4949
"list_session_tags",
5050
"assign_tag_to_session",
5151
"unassign_tag_from_session",
52-
// extension
53-
"get_extension_mapping",
54-
"list_extension_mappings",
55-
"upsert_extension_mapping",
5652
];
5753

5854
fn main() {

0 commit comments

Comments
 (0)