Skip to content
File

Blob: src/client/stores/db/bland-db.ts

typescript102 lines
1import Dexie, { type Table } from "dexie";
2import type {
3 Page,
4 Workspace,
5 WorkspaceMembershipSummary,
6 WorkspaceMember,
7 WorkspaceRole,
8 SharedWithMeItem,
9 SharedInboxWorkspaceSummary,
10} from "@/shared/types";
11 
12export type WorkspaceAccessMode = "member" | "shared";
13 
14export type PageAccessMode = "view" | "edit";
15 
16export interface MemberWorkspaceRow extends WorkspaceMembershipSummary {
17 /** Preserves the source API ordering across Dexie rehydration; read via
18 * `orderBy("rank")` so workspace-switcher + root-redirect fallback see the
19 * same order the server returned. */
20 rank: number;
21}
22 
23export interface WorkspaceReplicaRow {
24 id: string;
25 workspace: Workspace;
26 slug: string;
27 accessMode: WorkspaceAccessMode;
28 workspaceRole: WorkspaceRole | null;
29}
30 
31export type WorkspacePageRow = Page;
32 
33export type WorkspaceMemberRow = WorkspaceMember;
34 
35export interface PageAccessRow {
36 pageId: string;
37 mode: PageAccessMode;
38}
39 
40export interface SharedInboxItemRow {
41 pageId: string;
42 workspaceId: string;
43 rank: number;
44 item: SharedWithMeItem;
45}
46 
47export interface SharedInboxSummaryRow {
48 workspaceId: string;
49 rank: number;
50 summary: SharedInboxWorkspaceSummary;
51}
52 
53export interface LastVisitedPageRow {
54 workspaceId: string;
55 pageId: string;
56}
57 
58export type WorkspaceMetaKey = "owner" | "lastVisitedWorkspaceId";
59 
60export interface WorkspaceMetaRow {
61 key: WorkspaceMetaKey;
62 value: string | null;
63}
64 
65export class BlandDatabase extends Dexie {
66 memberWorkspaces!: Table<MemberWorkspaceRow, string>;
67 workspaceReplicas!: Table<WorkspaceReplicaRow, string>;
68 workspacePages!: Table<WorkspacePageRow, string>;
69 workspaceMembers!: Table<WorkspaceMemberRow, [string, string]>;
70 pageAccess!: Table<PageAccessRow, string>;
71 sharedInboxItems!: Table<SharedInboxItemRow, string>;
72 sharedInboxWorkspaceSummaries!: Table<SharedInboxSummaryRow, string>;
73 lastVisitedPages!: Table<LastVisitedPageRow, string>;
74 workspaceMeta!: Table<WorkspaceMetaRow, string>;
75 
76 constructor(name: string) {
77 super(name);
78 // parent_id and archived_at are intentionally not indexed: Dexie cannot
79 // index null values. We build tree shape and filter archived rows in
80 // memory from the flat page projection. Index property paths match the
81 // row key names verbatim (snake_case for Page/WorkspaceMember rows, which
82 // reflect the shared API shape).
83 this.version(1).stores({
84 memberWorkspaces: "id, slug, rank",
85 workspaceReplicas: "id, slug",
86 workspacePages: "id, workspace_id",
87 workspaceMembers: "[workspace_id+user_id], workspace_id",
88 pageAccess: "pageId",
89 sharedInboxItems: "pageId, workspaceId, rank",
90 sharedInboxWorkspaceSummaries: "workspaceId, rank",
91 lastVisitedPages: "workspaceId",
92 workspaceMeta: "key",
93 });
94 }
95}
96 
97export function createDb(name: string): BlandDatabase {
98 return new BlandDatabase(name);
99}
100 
101export const db: BlandDatabase = createDb("bland");