diff --git a/frontend/src/adapters/index.ts b/frontend/src/adapters/index.ts new file mode 100644 index 0000000..45a6e0f --- /dev/null +++ b/frontend/src/adapters/index.ts @@ -0,0 +1,19 @@ +// The data source the whole app uses. Import `repo` from here — never reach for +// `api/client` in stores/views. +// +// Today it always resolves to the REST source. M10.5 adds the offline desktop +// source (`local.ts`, over Tauri `invoke`) and flips this to a runtime choice: +// +// import { isDesktop } from "../desktop/bridge"; +// import { local } from "./local"; +// export const repo: Repo = isDesktop() && !hasConfiguredServer() ? local : rest; +// +// so a fresh desktop launch runs fully offline, and a server-connected one keeps +// using REST. Web is unaffected. + +import { rest } from "./rest"; +import type { Repo } from "./repo"; + +export const repo: Repo = rest; + +export type { Repo } from "./repo"; diff --git a/frontend/src/adapters/repo.ts b/frontend/src/adapters/repo.ts new file mode 100644 index 0000000..9ea20ee --- /dev/null +++ b/frontend/src/adapters/repo.ts @@ -0,0 +1,140 @@ +// The data-source seam. Stores/views talk to this typed repository interface +// instead of reaching for the HTTP client directly, so the SAME UI can run +// against the REST backend (web + a server-connected desktop) or a fully-local +// on-device source (offline desktop). `rest.ts` implements it over `api/client`; +// `local.ts` (M10.5) implements it over Tauri `invoke`; `index.ts` picks one. +// +// Keep this interface a thin, semantic mirror of the current calls: every method +// maps 1:1 to a backend operation and returns the same shape the stores already +// consume. Client-side logic (list reconciliation, optimistic updates, toasts) +// stays in the stores — the repo is data access only. + +import type { NoteColor } from "../notes/colors"; +import type { Note, NoteFacets, NoteView, NoteKind, NoteRevision } from "../stores/notes"; +import type { Label } from "../stores/labels"; +import type { SavedFilter } from "../stores/savedFilters"; +import type { Device } from "../stores/devices"; +import type { TitleEntry } from "../stores/titles"; +import type { User } from "../stores/session"; +import type { PublicConfig } from "../stores/config"; + +// ---- notes payload shapes ---------------------------------------------------- + +// The full board query the GET /api/notes endpoint accepts. `rest.ts` renders it +// to a query string (the one place that knows the URL shape); a local source +// reads the same fields structurally. +export interface NoteListQuery { + view: NoteView; + labelId?: string | null; + facets?: NoteFacets; + // The timeline view sorts by creation instead of the board's pinned/position order. + sort?: "created"; +} + +export interface NoteCreateInput { + title: string; + body: string; + color: NoteColor; + kind?: NoteKind; + items?: string[]; +} + +// The mutable subset of a note (PATCH /api/notes/:id). +export type NoteChanges = Partial< + Pick +>; + +export interface ChecklistItemChanges { + text?: string; + checked?: boolean; +} + +export interface Backlink { + id: string; + title: string; +} + +export interface ImportResult { + source: string; + imported: number; + skipped: number; +} + +export interface DeviceToken { + token: string; + device: Device; +} + +// ---- per-domain repositories ------------------------------------------------- + +export interface ConfigRepo { + get(): Promise; +} + +export interface AuthRepo { + me(): Promise; + login(email: string, password: string): Promise; + register(email: string, password: string, displayName: string): Promise; + logout(): Promise; +} + +export interface DevicesRepo { + list(): Promise; + create(name: string): Promise; + remove(id: string): Promise; +} + +export interface LabelsRepo { + list(): Promise; + create(name: string): Promise