Sync your Obsidian vault to an atproto PDS
community.obsidian.md/plugins/pds-sync
2.5 kB
74 lines
1import type { AtpClient } from "../atproto/client";
2
3/**
4 * A SyncTarget is one backend a note can be pushed to. The engine is backend-
5 * agnostic: it diffs notes and calls push/delete. This is the seam that lets us
6 * ship public + private today and slot in native atproto private-namespaces or
7 * `ats://` spaces later without touching the engine.
8 */
9
10/** Stable id for a target. Persisted in note frontmatter as `pds_target`. */
11export type TargetId = "standard-site" | "e2ee-pds" | "ats-space";
12
13/** Normalised note ready to push (frontmatter already stripped from body). */
14export interface NoteInput {
15 /** Vault-relative path, e.g. "notes/idea.md". */
16 path: string;
17 title: string;
18 /** Note body, no frontmatter. */
19 markdown: string;
20 /** Optional URL slug for public publishing. */
21 slug?: string;
22 /** ISO timestamp. */
23 publishedAt: string;
24 /** ISO timestamp, if known. */
25 updatedAt?: string;
26 /** The note's user frontmatter (minus our index keys) - preserved for private notes. */
27 frontmatter?: Record<string, unknown>;
28}
29
30/** Pointer to a pushed record. Persisted back into the note's frontmatter. */
31export interface RemoteRef {
32 rkey: string;
33 cid: string;
34 uri: string;
35}
36
37/** A record fetched from the PDS, decoded back into a note (decrypted if private). */
38export interface PulledNote {
39 ref: RemoteRef;
40 note: NoteInput;
41}
42
43/**
44 * Outcome of a push. "written" = the record was created/updated/recreated.
45 * "conflict" = the remote record changed under us; the caller resolves it
46 * (write `remote` as a conflict copy) rather than clobbering.
47 */
48export type PushResult =
49 | { status: "written"; ref: RemoteRef }
50 | { status: "conflict"; remote: PulledNote; current: RemoteRef };
51
52export interface SyncTarget {
53 readonly id: TargetId;
54 /** Collection NSID this target writes to. */
55 readonly collection: string;
56 /** Human label for notices/logs. */
57 readonly label: string;
58
59 /** True if this target is configured well enough to run. */
60 isReady(): boolean;
61 /** Reason it is not ready, for surfacing to the user. */
62 readyError(): string;
63
64 /** Create or update the record for a note (compare-and-swap). `existing` is its prior ref. */
65 push(
66 client: AtpClient,
67 note: NoteInput,
68 existing?: RemoteRef,
69 ): Promise<PushResult>;
70 /** Delete the record for an orphaned note. */
71 delete(client: AtpClient, ref: RemoteRef): Promise<void>;
72 /** Fetch every record in this collection, decoded into notes (for pull/restore). */
73 list(client: AtpClient): Promise<PulledNote[]>;
74}