desktop M10.3: frontend data-source adapter seam (repo interface + rest.ts)
Extract a typed repository interface (adapters/repo.ts) from the scattered store/view -> api.* calls, backed by adapters/rest.ts (verbatim HTTP mapping) and selected through adapters/index.ts. Every store and the notes-facing views now depend on `repo`, never the HTTP client directly -- the seam the offline local source (M10.5, over Tauri invoke) plugs into next. Behavior-preserving for web: rest.ts maps each semantic method to the exact endpoint the code called before; query-string and multipart building moved out of the stores/views into rest.ts (the one place that knows the URL shape). Client-side logic (reconcile/sort/optimistic reorder/toasts) stays in the stores. GraphView + admin SettingsView keep direct api calls -- out of the offline-core scope (M10.5 is board/editor/capture/search/filter/labels/ checklists/reminders). Task 1992. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -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<Note, "title" | "body" | "color" | "kind" | "pinned" | "archived" | "remind_at" | "recurrence">
|
||||
>;
|
||||
|
||||
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<PublicConfig>;
|
||||
}
|
||||
|
||||
export interface AuthRepo {
|
||||
me(): Promise<User>;
|
||||
login(email: string, password: string): Promise<User>;
|
||||
register(email: string, password: string, displayName: string): Promise<User>;
|
||||
logout(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface DevicesRepo {
|
||||
list(): Promise<Device[]>;
|
||||
create(name: string): Promise<DeviceToken>;
|
||||
remove(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface LabelsRepo {
|
||||
list(): Promise<Label[]>;
|
||||
create(name: string): Promise<Label>;
|
||||
rename(id: string, name: string): Promise<Label>;
|
||||
setColor(id: string, color: string): Promise<Label>;
|
||||
remove(id: string): Promise<void>;
|
||||
merge(sourceId: string, into: string): Promise<Label>;
|
||||
}
|
||||
|
||||
export interface NotesRepo {
|
||||
list(query: NoteListQuery): Promise<Note[]>;
|
||||
get(id: string): Promise<Note>;
|
||||
create(input: NoteCreateInput): Promise<Note>;
|
||||
createTitled(title: string): Promise<Note>;
|
||||
update(id: string, changes: NoteChanges): Promise<Note>;
|
||||
completeReminder(id: string): Promise<Note>;
|
||||
snoozeReminder(id: string, minutes: number): Promise<Note>;
|
||||
setLabels(id: string, labelIds: string[]): Promise<Note>;
|
||||
addItem(id: string, text: string): Promise<Note>;
|
||||
updateItem(id: string, itemId: string, changes: ChecklistItemChanges): Promise<Note>;
|
||||
deleteItem(id: string, itemId: string): Promise<Note>;
|
||||
uploadAttachment(id: string, file: File): Promise<Note>;
|
||||
deleteAttachment(id: string, attId: string): Promise<Note>;
|
||||
unfurl(id: string, url: string): Promise<Note>;
|
||||
deletePreview(id: string, previewId: string): Promise<Note>;
|
||||
import(file: File): Promise<ImportResult>;
|
||||
reorder(orderedIds: string[]): Promise<void>;
|
||||
trash(id: string): Promise<Note>;
|
||||
restore(id: string): Promise<Note>;
|
||||
deleteForever(id: string): Promise<void>;
|
||||
revisions(id: string): Promise<NoteRevision[]>;
|
||||
restoreRevision(id: string, revId: string): Promise<Note>;
|
||||
reminders(): Promise<Note[]>;
|
||||
titles(): Promise<TitleEntry[]>;
|
||||
search(q: string): Promise<Note[]>;
|
||||
backlinks(id: string): Promise<Backlink[]>;
|
||||
linkSearch(q: string): Promise<TitleEntry[]>;
|
||||
}
|
||||
|
||||
export interface SavedFiltersRepo {
|
||||
list(): Promise<SavedFilter[]>;
|
||||
create(name: string, params: NoteFacets): Promise<SavedFilter>;
|
||||
remove(id: string): Promise<void>;
|
||||
rename(id: string, name: string): Promise<SavedFilter>;
|
||||
}
|
||||
|
||||
export interface Repo {
|
||||
config: ConfigRepo;
|
||||
auth: AuthRepo;
|
||||
devices: DevicesRepo;
|
||||
labels: LabelsRepo;
|
||||
notes: NotesRepo;
|
||||
savedFilters: SavedFiltersRepo;
|
||||
}
|
||||
Reference in New Issue
Block a user