feat(sync): sync Web and Desktop projects with Host Server SQLite and Main Project Directory
This commit is contained in:
@@ -13,7 +13,7 @@ import type {
|
||||
import type { StorageAdapter, StoreEvent } from "./storage.js";
|
||||
|
||||
export interface ServerClientOptions {
|
||||
/** Base URL of the PubScript server, e.g. `http://localhost:8080`. */
|
||||
/** Base URL of the PubScript server, e.g. `http://localhost:8080` or `""` for same-origin. */
|
||||
baseUrl: string;
|
||||
/** Provide a custom fetch (e.g. for tests). Defaults to global fetch. */
|
||||
fetch?: typeof fetch;
|
||||
@@ -37,6 +37,7 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
if (token) headers.authorization = `Bearer ${token}`;
|
||||
const res = await fetchImpl(`${opts.baseUrl}${path}`, {
|
||||
method,
|
||||
credentials: "include",
|
||||
headers,
|
||||
body: body === undefined ? null : JSON.stringify(body),
|
||||
});
|
||||
@@ -53,8 +54,9 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
return r.projects;
|
||||
},
|
||||
|
||||
async createProject(p: NewProject): Promise<Project> {
|
||||
async createProject(p: NewProject & { id?: string }): Promise<Project> {
|
||||
const r = await req<{ project: Project }>("POST", "/api/projects", {
|
||||
...(p.id ? { id: p.id } : {}),
|
||||
title: p.title,
|
||||
language: p.language,
|
||||
cover: p.cover,
|
||||
@@ -67,8 +69,14 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
return r.project;
|
||||
},
|
||||
|
||||
async deleteProject(id: string): Promise<void> {
|
||||
await req("DELETE", `/api/projects/${id}`);
|
||||
},
|
||||
|
||||
async createBook(b: { projectId: string; title: string }): Promise<Book> {
|
||||
const r = await req<{ book: Book }>("POST", `/api/projects/${b.projectId}/books`, { title: b.title });
|
||||
const r = await req<{ book: Book }>("POST", `/api/projects/${b.projectId}/books`, {
|
||||
title: b.title,
|
||||
});
|
||||
return r.book;
|
||||
},
|
||||
|
||||
@@ -86,6 +94,9 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
await req("POST", `/api/chapters/${doc.id}/autosave`, {
|
||||
rev: doc.rev,
|
||||
doc: doc.doc,
|
||||
title: doc.title,
|
||||
projectId: doc.projectId,
|
||||
bookId: doc.bookId,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -98,23 +109,43 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
}
|
||||
},
|
||||
|
||||
async deleteChapter(id: string): Promise<void> {
|
||||
await req("DELETE", `/api/chapters/${id}`);
|
||||
},
|
||||
|
||||
async saveEntity(e: Entity): Promise<void> {
|
||||
if ("fields" in e) {
|
||||
// WorldItem — the server nests the world kind inside `fields`.
|
||||
await req("POST", `/api/projects/${e.projectId}/entities`, {
|
||||
id: e.id, kind: "world", name: e.name, fields: { kind: e.kind, ...e.fields },
|
||||
id: e.id,
|
||||
kind: "world",
|
||||
name: e.name,
|
||||
fields: { kind: e.kind, ...e.fields },
|
||||
});
|
||||
} else if ("body" in e) {
|
||||
// Note
|
||||
await req("POST", `/api/projects/${e.projectId}/entities`, {
|
||||
id: e.id, kind: "note", body: e.body, pinned: e.pinned, tags: e.tags, linkedTo: e.linkedTo,
|
||||
id: e.id,
|
||||
kind: "note",
|
||||
body: e.body,
|
||||
pinned: e.pinned,
|
||||
tags: e.tags,
|
||||
linkedTo: e.linkedTo,
|
||||
});
|
||||
} else {
|
||||
// Character
|
||||
await req("POST", `/api/projects/${e.projectId}/entities`, {
|
||||
id: e.id, kind: "character", name: e.name, role: e.role, bio: e.bio,
|
||||
appearance: e.appearance, voice: e.voice, relationships: e.relationships,
|
||||
arc: e.arc, status: e.status, tags: e.tags,
|
||||
id: e.id,
|
||||
kind: "character",
|
||||
name: e.name,
|
||||
role: e.role,
|
||||
bio: e.bio,
|
||||
appearance: e.appearance,
|
||||
voice: e.voice,
|
||||
relationships: e.relationships,
|
||||
arc: e.arc,
|
||||
status: e.status,
|
||||
tags: e.tags,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -212,8 +243,6 @@ export function createServerClient(opts: ServerClientOptions): StorageAdapter {
|
||||
},
|
||||
|
||||
onEvent(_cb: (e: StoreEvent) => void): () => void {
|
||||
// Live updates arrive over the `/api/ws` socket; wiring that up is the
|
||||
// app's concern. For the contract, return a no-op unsubscribe.
|
||||
return () => {};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user