@book.dev/sdk 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/account.d.ts +57 -0
- package/dist/account.js +104 -0
- package/dist/account.js.map +1 -0
- package/dist/ai.d.ts +264 -0
- package/dist/ai.js +36 -0
- package/dist/ai.js.map +1 -0
- package/dist/backup.d.ts +52 -0
- package/dist/backup.js +35 -0
- package/dist/backup.js.map +1 -0
- package/dist/bookFolder.d.ts +44 -0
- package/dist/bookFolder.js +98 -0
- package/dist/bookFolder.js.map +1 -0
- package/dist/bookfile.d.ts +42 -0
- package/dist/bookfile.js +159 -0
- package/dist/bookfile.js.map +1 -0
- package/dist/client.d.ts +279 -0
- package/dist/client.js +502 -0
- package/dist/client.js.map +1 -0
- package/dist/connection.d.ts +20 -0
- package/dist/connection.js +77 -0
- package/dist/connection.js.map +1 -0
- package/dist/content.d.ts +38 -0
- package/dist/content.js +107 -0
- package/dist/content.js.map +1 -0
- package/dist/database.d.ts +687 -0
- package/dist/database.js +1082 -0
- package/dist/database.js.map +1 -0
- package/dist/formula.d.ts +41 -0
- package/dist/formula.js +469 -0
- package/dist/formula.js.map +1 -0
- package/dist/forwarding/challenge.d.ts +35 -0
- package/dist/forwarding/challenge.js +45 -0
- package/dist/forwarding/challenge.js.map +1 -0
- package/dist/forwarding/encoding.d.ts +14 -0
- package/dist/forwarding/encoding.js +54 -0
- package/dist/forwarding/encoding.js.map +1 -0
- package/dist/forwarding/forwardingClient.d.ts +76 -0
- package/dist/forwarding/forwardingClient.js +128 -0
- package/dist/forwarding/forwardingClient.js.map +1 -0
- package/dist/forwarding/index.d.ts +5 -0
- package/dist/forwarding/index.js +14 -0
- package/dist/forwarding/index.js.map +1 -0
- package/dist/forwarding/siteKey.d.ts +12 -0
- package/dist/forwarding/siteKey.js +36 -0
- package/dist/forwarding/siteKey.js.map +1 -0
- package/dist/forwarding/tunnelClient.d.ts +56 -0
- package/dist/forwarding/tunnelClient.js +206 -0
- package/dist/forwarding/tunnelClient.js.map +1 -0
- package/dist/forwarding/tunnelProtocol.d.ts +45 -0
- package/dist/forwarding/tunnelProtocol.js +29 -0
- package/dist/forwarding/tunnelProtocol.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/mtime.d.ts +51 -0
- package/dist/mtime.js +84 -0
- package/dist/mtime.js.map +1 -0
- package/dist/pageProperties.d.ts +80 -0
- package/dist/pageProperties.js +97 -0
- package/dist/pageProperties.js.map +1 -0
- package/dist/plugins.d.ts +110 -0
- package/dist/plugins.js +113 -0
- package/dist/plugins.js.map +1 -0
- package/dist/routes.d.ts +89 -0
- package/dist/routes.js +89 -0
- package/dist/routes.js.map +1 -0
- package/dist/sampleDocument.d.ts +25 -0
- package/dist/sampleDocument.js +67 -0
- package/dist/sampleDocument.js.map +1 -0
- package/dist/suggestions.d.ts +126 -0
- package/dist/suggestions.js +21 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/templates.d.ts +35 -0
- package/dist/templates.js +555 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +146 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { pageToBookHtml, bookHtmlToPage, slugify } from './bookfile';
|
|
2
|
+
/** Lossless structured sidecar, parsed back by {@link parseBookFolder}. */
|
|
3
|
+
export const SPACE_BUNDLE_FILE = 'openbook.space.json';
|
|
4
|
+
const MAX_DEPTH = 64;
|
|
5
|
+
const pageIcon = (page) => {
|
|
6
|
+
const icon = page.properties?.sys_icon;
|
|
7
|
+
return typeof icon === 'string' ? icon : null;
|
|
8
|
+
};
|
|
9
|
+
const folderName = (root) => `${slugify(root.name ?? 'untitled')}--${root.id.slice(0, 8)}`;
|
|
10
|
+
const fileName = (page) => `${slugify(page.name ?? 'untitled')}--${page.id.slice(0, 8)}.html`;
|
|
11
|
+
/** Topmost ancestor: walk `parentId`, or a row's database-host page, to the root. */
|
|
12
|
+
function rootOf(page, byId, dbHost) {
|
|
13
|
+
let root = page;
|
|
14
|
+
for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
|
|
15
|
+
let parentId = null;
|
|
16
|
+
if (root.parentId)
|
|
17
|
+
parentId = root.parentId;
|
|
18
|
+
else if (root.databaseId)
|
|
19
|
+
parentId = dbHost.get(root.databaseId) ?? null;
|
|
20
|
+
const parent = parentId ? byId.get(parentId) : null;
|
|
21
|
+
if (!parent)
|
|
22
|
+
break;
|
|
23
|
+
root = parent;
|
|
24
|
+
}
|
|
25
|
+
return root;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Serialise a space to its on-disk files. By default includes the lossless
|
|
29
|
+
* {@link SPACE_BUNDLE_FILE}; pass `includeBundle: false` for the human-readable
|
|
30
|
+
* HTML files only.
|
|
31
|
+
*/
|
|
32
|
+
export function spaceToBookFiles(snapshot, opts = {}) {
|
|
33
|
+
const { pages, databases } = snapshot;
|
|
34
|
+
const byId = new Map(pages.map((p) => [p.id, p]));
|
|
35
|
+
const dbHost = new Map(databases.map((d) => [d.id, d.pageId]));
|
|
36
|
+
const files = [];
|
|
37
|
+
for (const page of pages) {
|
|
38
|
+
const root = rootOf(page, byId, dbHost);
|
|
39
|
+
const html = pageToBookHtml({
|
|
40
|
+
id: page.id,
|
|
41
|
+
name: page.name,
|
|
42
|
+
icon: pageIcon(page),
|
|
43
|
+
updatedAt: page.updatedAt,
|
|
44
|
+
data: page.data,
|
|
45
|
+
});
|
|
46
|
+
files.push({ path: `${folderName(root)}/${fileName(page)}`, contents: html });
|
|
47
|
+
}
|
|
48
|
+
if (opts.includeBundle !== false) {
|
|
49
|
+
files.push({ path: SPACE_BUNDLE_FILE, contents: JSON.stringify(snapshot, null, 2) });
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reconstruct a space from a folder's files. Prefers the lossless
|
|
55
|
+
* {@link SPACE_BUNDLE_FILE} when present; otherwise falls back to parsing the
|
|
56
|
+
* `.html` files into a flat list of pages (no databases, no nesting — the most
|
|
57
|
+
* a human-readable folder can recover). Returns `null` if nothing parseable was
|
|
58
|
+
* found, so the caller can surface "not an OpenBook folder".
|
|
59
|
+
*/
|
|
60
|
+
export function parseBookFolder(files) {
|
|
61
|
+
const bundle = files.find((f) => f.path === SPACE_BUNDLE_FILE || f.path.endsWith(`/${SPACE_BUNDLE_FILE}`));
|
|
62
|
+
if (bundle) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(bundle.contents);
|
|
65
|
+
if (Array.isArray(parsed.pages)) {
|
|
66
|
+
return { pages: parsed.pages, databases: Array.isArray(parsed.databases) ? parsed.databases : [] };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Corrupt bundle — fall through to the HTML files.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const pages = [];
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
if (!file.path.endsWith('.html'))
|
|
76
|
+
continue;
|
|
77
|
+
const record = bookHtmlToPage(file.contents);
|
|
78
|
+
if (record)
|
|
79
|
+
pages.push(recordToPage(record));
|
|
80
|
+
}
|
|
81
|
+
return pages.length ? { pages, databases: [] } : null;
|
|
82
|
+
}
|
|
83
|
+
/** Inflate a parsed `.book.html` record into a minimal StoredPage for import. */
|
|
84
|
+
function recordToPage(record) {
|
|
85
|
+
return {
|
|
86
|
+
id: record.id,
|
|
87
|
+
name: record.name,
|
|
88
|
+
data: record.data,
|
|
89
|
+
hostedDatabaseId: null,
|
|
90
|
+
databaseId: null,
|
|
91
|
+
parentId: null,
|
|
92
|
+
properties: record.icon ? { sys_icon: record.icon } : {},
|
|
93
|
+
deletedAt: null,
|
|
94
|
+
createdAt: record.updatedAt,
|
|
95
|
+
updatedAt: record.updatedAt,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=bookFolder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bookFolder.js","sourceRoot":"","sources":["../src/bookFolder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The on-disk **book file** format (OB-134): one HTML file per page. Each file
|
|
3
|
+
* is human-readable (so external sync/backup tools and editors see real content)
|
|
4
|
+
* yet round-trips losslessly back into pglite, because it embeds the canonical
|
|
5
|
+
* {@link PageSnapshot} as a JSON island alongside the rendered body.
|
|
6
|
+
*
|
|
7
|
+
* Every rendered block carries a stable `data-block-id` and a `data-block-mtime`,
|
|
8
|
+
* and the page itself carries `data-page-id` / `data-page-updated` (the DB
|
|
9
|
+
* `updatedAt` the file was rendered from — the conflict base) / `data-page-mtime`.
|
|
10
|
+
* Those attributes are the change signal the watcher and conflict resolver read.
|
|
11
|
+
*
|
|
12
|
+
* Pure and isomorphic: rendering and the island parse use no DOM, so the server
|
|
13
|
+
* mirror can emit and re-read files directly. The readable body mirrors the
|
|
14
|
+
* `export/` look (the `ob-*` class names) without depending on the ui package.
|
|
15
|
+
*/
|
|
16
|
+
import type { PageSnapshot } from './types';
|
|
17
|
+
/** A page reduced to what a book file needs to carry. */
|
|
18
|
+
export interface BookPageRecord {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string | null;
|
|
21
|
+
icon: string | null;
|
|
22
|
+
/** The DB `updatedAt` this file was rendered from — the conflict base. */
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
data: PageSnapshot;
|
|
25
|
+
}
|
|
26
|
+
/** Lower-case, dash-separated, filesystem-safe slug (for the on-disk filename). */
|
|
27
|
+
export declare function slugify(input: string, fallback?: string): string;
|
|
28
|
+
/** Render a page to its on-disk book-file HTML (readable body + canonical island). */
|
|
29
|
+
export declare function pageToBookHtml(record: BookPageRecord): string;
|
|
30
|
+
/** Cheap metadata read from the `<html>` tag — id + conflict base + name. */
|
|
31
|
+
export declare function readBookHtmlMeta(html: string): {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
} | null;
|
|
36
|
+
/**
|
|
37
|
+
* Parse a book file back into its page record via the canonical JSON island.
|
|
38
|
+
* Returns `null` if the file isn't an OpenBook book page or the island is
|
|
39
|
+
* missing/corrupt. This is the lossless re-import path (sync clients, restored
|
|
40
|
+
* backups, moved files all preserve the island).
|
|
41
|
+
*/
|
|
42
|
+
export declare function bookHtmlToPage(html: string): BookPageRecord | null;
|
package/dist/bookfile.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { latestBlockMtime } from './mtime';
|
|
2
|
+
const MARKER = 'application/openbook+json';
|
|
3
|
+
const esc = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
4
|
+
/** Lower-case, dash-separated, filesystem-safe slug (for the on-disk filename). */
|
|
5
|
+
export function slugify(input, fallback = 'untitled') {
|
|
6
|
+
const slug = (input || '')
|
|
7
|
+
.normalize('NFKD')
|
|
8
|
+
.replace(/[̀-ͯ]/g, '')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
11
|
+
.replace(/^-+|-+$/g, '')
|
|
12
|
+
.slice(0, 80);
|
|
13
|
+
return slug || fallback;
|
|
14
|
+
}
|
|
15
|
+
/** Ordered top-level blocks with resolved ids (matching sdk `snapshotBlocks`). */
|
|
16
|
+
function orderedBlocks(data) {
|
|
17
|
+
const blockdoc = data.blockdoc;
|
|
18
|
+
const raw = data.editor === 'blocks' && Array.isArray(blockdoc?.blocks)
|
|
19
|
+
? blockdoc.blocks
|
|
20
|
+
: (data.editorjs?.blocks ?? []);
|
|
21
|
+
return raw.map((b, i) => ({ id: typeof b.id === 'string' && b.id ? b.id : `b${i}`, type: b.type ?? 'unknown', block: b }));
|
|
22
|
+
}
|
|
23
|
+
/** Inline HTML for a block-editor rich-text run. */
|
|
24
|
+
function runHtml(run) {
|
|
25
|
+
let out = esc(run.t).replace(/\n/g, '<br>');
|
|
26
|
+
const a = run.a ?? {};
|
|
27
|
+
if (a.c)
|
|
28
|
+
out = `<code>${out}</code>`;
|
|
29
|
+
if (a.b)
|
|
30
|
+
out = `<strong>${out}</strong>`;
|
|
31
|
+
if (a.i)
|
|
32
|
+
out = `<em>${out}</em>`;
|
|
33
|
+
if (a.u)
|
|
34
|
+
out = `<u>${out}</u>`;
|
|
35
|
+
if (a.s)
|
|
36
|
+
out = `<s>${out}</s>`;
|
|
37
|
+
if (typeof a.m === 'string')
|
|
38
|
+
out = `<a class="ob-mention" data-page-id="${esc(a.m)}">${out}</a>`;
|
|
39
|
+
else if (typeof a.a === 'string')
|
|
40
|
+
out = `<a href="${esc(a.a)}">${out}</a>`;
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
const runsHtml = (runs) => (runs ?? []).map(runHtml).join('');
|
|
44
|
+
/** Best-effort readable inner HTML for one block (the island stays authoritative). */
|
|
45
|
+
function blockInnerHtml(type, block) {
|
|
46
|
+
const d = block.data ?? {};
|
|
47
|
+
// Block-editor pages carry rich-text runs; EditorJS pages carry HTML strings.
|
|
48
|
+
const textHtml = block.text ? runsHtml(block.text) : typeof d.text === 'string' ? d.text : '';
|
|
49
|
+
switch (type) {
|
|
50
|
+
case 'heading':
|
|
51
|
+
case 'header': {
|
|
52
|
+
const level = Math.min(6, Math.max(1, Number(block.props?.level ?? d.level ?? 2)));
|
|
53
|
+
return { tag: `h${level}`, inner: textHtml };
|
|
54
|
+
}
|
|
55
|
+
case 'quote':
|
|
56
|
+
return { tag: 'blockquote', inner: textHtml };
|
|
57
|
+
case 'code':
|
|
58
|
+
return { tag: 'pre', inner: `<code>${esc(block.text ? (block.text.map((r) => r.t).join('')) : String(d.code ?? ''))}</code>` };
|
|
59
|
+
case 'list': {
|
|
60
|
+
const items = Array.isArray(d.items) ? d.items : [];
|
|
61
|
+
const ordered = d.style === 'ordered';
|
|
62
|
+
const lis = items
|
|
63
|
+
.map((it) => `<li>${typeof it === 'string' ? it : esc(String(it?.content ?? ''))}</li>`)
|
|
64
|
+
.join('');
|
|
65
|
+
return { tag: ordered ? 'ol' : 'ul', inner: lis };
|
|
66
|
+
}
|
|
67
|
+
case 'todo':
|
|
68
|
+
case 'checklist': {
|
|
69
|
+
const checked = block.props?.checked === true || d.checked === true;
|
|
70
|
+
return { tag: 'p', inner: `<input type="checkbox"${checked ? ' checked' : ''} disabled> ${textHtml}` };
|
|
71
|
+
}
|
|
72
|
+
case 'delimiter':
|
|
73
|
+
case 'divider':
|
|
74
|
+
return { tag: 'hr', inner: '' };
|
|
75
|
+
case 'paragraph':
|
|
76
|
+
return { tag: 'p', inner: textHtml };
|
|
77
|
+
default:
|
|
78
|
+
// Unknown / rich blocks (charts, tables, kit, …): readable text only — the
|
|
79
|
+
// JSON island carries their full data for a faithful re-import.
|
|
80
|
+
return { tag: 'div', inner: textHtml || `<em class="ob-raw">${esc(type)} block</em>` };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Render a page to its on-disk book-file HTML (readable body + canonical island). */
|
|
84
|
+
export function pageToBookHtml(record) {
|
|
85
|
+
const { id, name, icon, updatedAt, data } = record;
|
|
86
|
+
const title = (name ?? '').trim() || 'Untitled';
|
|
87
|
+
const mtime = new Map(data.mtimes ?? []);
|
|
88
|
+
const pageMtime = latestBlockMtime(data) ?? updatedAt;
|
|
89
|
+
const body = orderedBlocks(data)
|
|
90
|
+
.map(({ id: blockId, type, block }) => {
|
|
91
|
+
const { tag, inner } = blockInnerHtml(type, block);
|
|
92
|
+
const attrs = `data-block-id="${esc(blockId)}" data-block-type="${esc(type)}" data-block-mtime="${esc(mtime.get(blockId) ?? updatedAt)}"`;
|
|
93
|
+
return tag === 'hr' ? ` <hr ${attrs}>` : ` <${tag} ${attrs}>${inner}</${tag}>`;
|
|
94
|
+
})
|
|
95
|
+
.join('\n');
|
|
96
|
+
// The island JSON is escaped so a literal `</script>` in content can't close
|
|
97
|
+
// the tag early; `<\/` is still valid JSON and parses back to `</`.
|
|
98
|
+
const island = JSON.stringify({ version: 1, id, name, icon, updatedAt, data }).replace(/<\//g, '<\\/');
|
|
99
|
+
return `<!doctype html>
|
|
100
|
+
<html lang="en" data-openbook="book-page" data-page-id="${esc(id)}" data-page-name="${esc(title)}" data-page-updated="${esc(updatedAt)}" data-page-mtime="${esc(pageMtime)}">
|
|
101
|
+
<head>
|
|
102
|
+
<meta charset="utf-8">
|
|
103
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
104
|
+
<title>${esc(title)}</title>
|
|
105
|
+
</head>
|
|
106
|
+
<body class="ob-page">
|
|
107
|
+
<article>
|
|
108
|
+
<h1 class="ob-page-title">${icon ? `${esc(icon)} ` : ''}${esc(title)}</h1>
|
|
109
|
+
${body}
|
|
110
|
+
</article>
|
|
111
|
+
<script type="${MARKER}" data-openbook-snapshot>
|
|
112
|
+
${island}
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
/** Cheap metadata read from the `<html>` tag — id + conflict base + name. */
|
|
119
|
+
export function readBookHtmlMeta(html) {
|
|
120
|
+
const tag = html.match(/<html\b[^>]*\bdata-openbook="book-page"[^>]*>/i)?.[0];
|
|
121
|
+
if (!tag)
|
|
122
|
+
return null;
|
|
123
|
+
const attr = (n) => {
|
|
124
|
+
const m = tag.match(new RegExp(`\\b${n}="([^"]*)"`, 'i'));
|
|
125
|
+
return m ? unesc(m[1]) : '';
|
|
126
|
+
};
|
|
127
|
+
const id = attr('data-page-id');
|
|
128
|
+
if (!id)
|
|
129
|
+
return null;
|
|
130
|
+
return { id, name: attr('data-page-name'), updatedAt: attr('data-page-updated') };
|
|
131
|
+
}
|
|
132
|
+
const unesc = (s) => s.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&');
|
|
133
|
+
/**
|
|
134
|
+
* Parse a book file back into its page record via the canonical JSON island.
|
|
135
|
+
* Returns `null` if the file isn't an OpenBook book page or the island is
|
|
136
|
+
* missing/corrupt. This is the lossless re-import path (sync clients, restored
|
|
137
|
+
* backups, moved files all preserve the island).
|
|
138
|
+
*/
|
|
139
|
+
export function bookHtmlToPage(html) {
|
|
140
|
+
const m = html.match(new RegExp(`<script[^>]*type="${MARKER.replace(/[/+]/g, '\\$&')}"[^>]*>([\\s\\S]*?)</script>`, 'i'));
|
|
141
|
+
if (!m)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(m[1].trim());
|
|
145
|
+
if (!parsed.id || !parsed.data)
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
id: parsed.id,
|
|
149
|
+
name: parsed.name ?? null,
|
|
150
|
+
icon: parsed.icon ?? null,
|
|
151
|
+
updatedAt: parsed.updatedAt ?? '',
|
|
152
|
+
data: parsed.data,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=bookfile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bookfile.js","sourceRoot":"","sources":["../src/bookfile.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type { PluginPackage, StoredPlugin } from './plugins';
|
|
2
|
+
import type { AgentChatEvent, AgentChatMessage, AgentChatOptions, AiConfig, AiSearchResponse, AiSkill, AiStatus, AiTasksResponse } from './ai';
|
|
3
|
+
import type { PageInput, PageMeta, StoredPage } from './types';
|
|
4
|
+
import type { ImportRequest, ImportResult } from './backup';
|
|
5
|
+
import type { DatabaseInput, DatabaseRow, DatabaseUpdate, RowInput, RowUpdate, StoredDatabase } from './database';
|
|
6
|
+
import type { CommentInput, StoredComment, StoredSuggestion, SuggestionInput, SuggestionStatus, SuggestionUpdate } from './suggestions';
|
|
7
|
+
/** Handlers for a single page's live update stream. */
|
|
8
|
+
export interface PageSubscription {
|
|
9
|
+
/** A newer version of the page was saved (by anyone). */
|
|
10
|
+
onPage?: (page: StoredPage) => void;
|
|
11
|
+
/** The page was deleted. */
|
|
12
|
+
onDeleted?: (id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Storage-agnostic data access used by the document UI.
|
|
16
|
+
*
|
|
17
|
+
* The desktop app and the web shell both talk to a server through the same
|
|
18
|
+
* {@link HttpDataClient} — the desktop just points it at its bundled local
|
|
19
|
+
* server, the web shell at a remote one. Swapping the target URL is how a user
|
|
20
|
+
* moves between "store locally" and "connect to an external server"; the
|
|
21
|
+
* document code never changes.
|
|
22
|
+
*/
|
|
23
|
+
export interface DataClient {
|
|
24
|
+
/** List all pages' metadata, most-recently-updated first. */
|
|
25
|
+
listPages(): Promise<PageMeta[]>;
|
|
26
|
+
/** Fetch a page by id, or `null` if it does not exist. */
|
|
27
|
+
getPage(id: string): Promise<StoredPage | null>;
|
|
28
|
+
/** Create or update a page (upsert keyed on `input.id`). */
|
|
29
|
+
savePage(input: PageInput): Promise<StoredPage>;
|
|
30
|
+
/** Update only a page's name (leaves its document data untouched). */
|
|
31
|
+
renamePage(id: string, name: string | null): Promise<StoredPage>;
|
|
32
|
+
/**
|
|
33
|
+
* Shallow-merge structured property values into a page (owner, verification,
|
|
34
|
+
* …), leaving its document content and other properties untouched. Used by
|
|
35
|
+
* the page properties panel for non-row pages (database rows use {@link updateRow}).
|
|
36
|
+
*/
|
|
37
|
+
setPageProperties(id: string, properties: Record<string, unknown>): Promise<StoredPage>;
|
|
38
|
+
/** List the live pages that link to `id` (via `@`-mentions), newest first. */
|
|
39
|
+
listBacklinks(id: string): Promise<PageMeta[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Move a page within the sidebar tree: set its parent (`null` = top level) and
|
|
42
|
+
* the full ordered list of sibling ids under that parent (including this page).
|
|
43
|
+
* Used by drag-to-reorder / drag-to-nest. Rejects a move that would create a
|
|
44
|
+
* cycle (nesting a page under itself or a descendant).
|
|
45
|
+
*/
|
|
46
|
+
movePage(id: string, move: {
|
|
47
|
+
parentId: string | null;
|
|
48
|
+
orderedIds: string[];
|
|
49
|
+
}): Promise<StoredPage>;
|
|
50
|
+
aiStatus(): Promise<AiStatus>;
|
|
51
|
+
aiSetConfig(config: AiConfig): Promise<AiConfig>;
|
|
52
|
+
aiIndex(): Promise<{
|
|
53
|
+
pages: number;
|
|
54
|
+
chunks: number;
|
|
55
|
+
}>;
|
|
56
|
+
aiSearch(query: string, limit?: number): Promise<AiSearchResponse>;
|
|
57
|
+
aiTasks(goal: string, context?: string): Promise<AiTasksResponse>;
|
|
58
|
+
aiDownloadModel(url?: string): Promise<AiStatus['download']>;
|
|
59
|
+
aiComplete(text: string, onToken: (token: string) => void, opts?: {
|
|
60
|
+
instruction?: string;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
}): Promise<string>;
|
|
63
|
+
aiGenerate(prompt: string, onToken: (token: string) => void, opts?: {
|
|
64
|
+
system?: string;
|
|
65
|
+
maxTokens?: number;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Run the workspace agent on a conversation. `onEvent` fires once per step
|
|
70
|
+
* (tool call, tool result, reasoning, proposals, final answer, error);
|
|
71
|
+
* resolves when the run ends. `opts` carries effort/thinking/skills overrides.
|
|
72
|
+
*/
|
|
73
|
+
agentChat(messages: AgentChatMessage[], onEvent: (event: AgentChatEvent) => void, opts?: AgentChatOptions): Promise<void>;
|
|
74
|
+
/** List the workspace's prompt/recipe skills. */
|
|
75
|
+
aiSkills(): Promise<AiSkill[]>;
|
|
76
|
+
/** Create or replace a prompt/recipe skill (keyed on its slug). */
|
|
77
|
+
aiSaveSkill(skill: AiSkill): Promise<AiSkill>;
|
|
78
|
+
/** Delete a prompt/recipe skill by name. */
|
|
79
|
+
aiDeleteSkill(name: string): Promise<boolean>;
|
|
80
|
+
listPlugins(): Promise<StoredPlugin[]>;
|
|
81
|
+
installPlugin(pkg: PluginPackage): Promise<StoredPlugin>;
|
|
82
|
+
setPluginEnabled(id: string, enabled: boolean): Promise<StoredPlugin>;
|
|
83
|
+
removePlugin(id: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Move a page (and its nested subtree) to the trash. Soft delete: the page is
|
|
86
|
+
* recoverable via {@link restorePage} until the server's cleanup job purges
|
|
87
|
+
* it. Resolves `true` if a live page was trashed.
|
|
88
|
+
*/
|
|
89
|
+
deletePage(id: string): Promise<boolean>;
|
|
90
|
+
/** Export the whole space: every live page (full data) + every database. */
|
|
91
|
+
exportSpace(): Promise<{
|
|
92
|
+
pages: StoredPage[];
|
|
93
|
+
databases: StoredDatabase[];
|
|
94
|
+
}>;
|
|
95
|
+
/** Restore a (client-selected) set of pages/databases; see {@link ImportRequest}. */
|
|
96
|
+
importSpace(req: ImportRequest): Promise<ImportResult>;
|
|
97
|
+
/** List the trash (most-recently-deleted first). */
|
|
98
|
+
listTrash(): Promise<PageMeta[]>;
|
|
99
|
+
/** Restore a trashed page, or `null` if it isn't in the trash. */
|
|
100
|
+
restorePage(id: string): Promise<StoredPage | null>;
|
|
101
|
+
/** Permanently delete one trashed page (and its subtree). `true` if removed. */
|
|
102
|
+
purgePage(id: string): Promise<boolean>;
|
|
103
|
+
/** Permanently empty the whole trash. Resolves the number of pages purged. */
|
|
104
|
+
emptyTrash(): Promise<number>;
|
|
105
|
+
/** Subscribe to a single page's live updates. Returns an unsubscribe fn. */
|
|
106
|
+
subscribePage(id: string, handlers: PageSubscription): () => void;
|
|
107
|
+
/** Subscribe to live page-list updates. Returns an unsubscribe fn. */
|
|
108
|
+
subscribePages(onList: (pages: PageMeta[]) => void): () => void;
|
|
109
|
+
/** Create a database for a host page. */
|
|
110
|
+
createDatabase(input: DatabaseInput): Promise<StoredDatabase>;
|
|
111
|
+
/** Fetch a database by id, or `null` if it does not exist. */
|
|
112
|
+
getDatabase(id: string): Promise<StoredDatabase | null>;
|
|
113
|
+
/** Fetch the database hosted by a page, or `null` if the page hosts none. */
|
|
114
|
+
getPageDatabase(pageId: string): Promise<StoredDatabase | null>;
|
|
115
|
+
/** Update a database's name and/or schema. */
|
|
116
|
+
updateDatabase(id: string, patch: DatabaseUpdate): Promise<StoredDatabase>;
|
|
117
|
+
/** Delete a database and all its row pages. Resolves `true` if removed. */
|
|
118
|
+
deleteDatabase(id: string): Promise<boolean>;
|
|
119
|
+
/** List a database's rows (projected: properties + exported cell values). */
|
|
120
|
+
listRows(databaseId: string): Promise<DatabaseRow[]>;
|
|
121
|
+
/** Create a row (a new page) inside a database. Returns the row page. */
|
|
122
|
+
createRow(databaseId: string, input?: RowInput): Promise<StoredPage>;
|
|
123
|
+
/** Update a row's title and/or manual property values. */
|
|
124
|
+
updateRow(databaseId: string, rowId: string, patch: RowUpdate): Promise<DatabaseRow>;
|
|
125
|
+
/** Set the manual order of a database's rows (full ordered id list). */
|
|
126
|
+
reorderRows(databaseId: string, orderedIds: string[]): Promise<void>;
|
|
127
|
+
/** Subscribe to a database's live row-list updates. Returns an unsubscribe fn. */
|
|
128
|
+
subscribeRows(databaseId: string, onRows: (rows: DatabaseRow[]) => void): () => void;
|
|
129
|
+
/** List a page's suggestions, newest first. `status` filters (e.g. only open). */
|
|
130
|
+
listSuggestions(pageId: string, status?: SuggestionStatus): Promise<StoredSuggestion[]>;
|
|
131
|
+
/** Persist a new suggestion (status defaults to `open`). */
|
|
132
|
+
createSuggestion(input: SuggestionInput): Promise<StoredSuggestion>;
|
|
133
|
+
/** Update a suggestion (today: its status). Returns the updated suggestion. */
|
|
134
|
+
updateSuggestion(id: string, patch: SuggestionUpdate): Promise<StoredSuggestion>;
|
|
135
|
+
/** Delete a suggestion (and its thread). Resolves `true` if removed. */
|
|
136
|
+
deleteSuggestion(id: string): Promise<boolean>;
|
|
137
|
+
/** List a page's comments, oldest first (so a thread reads top-to-bottom). */
|
|
138
|
+
listComments(pageId: string): Promise<StoredComment[]>;
|
|
139
|
+
/** Persist a new comment (on a suggestion or a block). */
|
|
140
|
+
createComment(input: CommentInput): Promise<StoredComment>;
|
|
141
|
+
/** Delete a comment. Resolves `true` if removed. */
|
|
142
|
+
deleteComment(id: string): Promise<boolean>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The `fetch` surface {@link HttpDataClient} needs. Defaults to the global
|
|
146
|
+
* `fetch`; the desktop injects an implementation that tunnels requests over its
|
|
147
|
+
* host IPC bridge (a Unix-socket server with no TCP port) instead.
|
|
148
|
+
*/
|
|
149
|
+
export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
150
|
+
/**
|
|
151
|
+
* The global `fetch`, wrapped so it's safe to store on an object and call as a
|
|
152
|
+
* property/method. WebKit (the desktop WKWebView) throws "Can only call
|
|
153
|
+
* Window.fetch on instances of Window" when `fetch` runs with a `this` that
|
|
154
|
+
* isn't the window — which is exactly what `obj.fetchImpl(...)` does once the
|
|
155
|
+
* bare global is assigned to a property. Calling the unqualified global here
|
|
156
|
+
* sidesteps that, so it's the right default wherever a {@link FetchLike} is
|
|
157
|
+
* stored and later invoked as a method.
|
|
158
|
+
*/
|
|
159
|
+
export declare const globalFetch: FetchLike;
|
|
160
|
+
/**
|
|
161
|
+
* The slice of `EventSource` {@link LiveStream} uses (named events + open/error
|
|
162
|
+
* via `addEventListener`, plus `close`). Defaults to a real `EventSource`; the
|
|
163
|
+
* desktop injects a source backed by host IPC events, since its server speaks
|
|
164
|
+
* over a socket the webview can't open an `EventSource` to.
|
|
165
|
+
*/
|
|
166
|
+
export interface LiveSourceLike {
|
|
167
|
+
addEventListener(type: string, handler: (event: {
|
|
168
|
+
data?: string;
|
|
169
|
+
}) => void): void;
|
|
170
|
+
close(): void;
|
|
171
|
+
}
|
|
172
|
+
/** Options for swapping {@link HttpDataClient}'s transport (desktop IPC). */
|
|
173
|
+
export interface HttpDataClientOptions {
|
|
174
|
+
/** Replacement for the global `fetch` (e.g. tunnel requests over host IPC). */
|
|
175
|
+
fetchImpl?: FetchLike;
|
|
176
|
+
/** Factory for the live-update source (e.g. an IPC-event-backed source). */
|
|
177
|
+
createLiveSource?: (url: string) => LiveSourceLike;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* {@link DataClient} backed by an OpenBook server's HTTP API. Isomorphic, and
|
|
181
|
+
* transport-pluggable: by default it uses the global `fetch` + `EventSource`
|
|
182
|
+
* (web, remote), but the desktop injects a `fetchImpl`/`createLiveSource` that
|
|
183
|
+
* tunnel over its host IPC bridge to a portless Unix-socket server.
|
|
184
|
+
*/
|
|
185
|
+
export declare class HttpDataClient implements DataClient {
|
|
186
|
+
private readonly baseUrl;
|
|
187
|
+
private readonly token?;
|
|
188
|
+
private readonly fetchImpl;
|
|
189
|
+
private readonly createLiveSource;
|
|
190
|
+
private live;
|
|
191
|
+
/**
|
|
192
|
+
* @param baseUrl Server base URL. May be empty when a `fetchImpl` resolves
|
|
193
|
+
* paths itself (the desktop IPC transport).
|
|
194
|
+
* @param token Optional access token required by a published (LAN) server;
|
|
195
|
+
* sent as `Authorization: Bearer` on requests and `?token=` on
|
|
196
|
+
* the SSE stream (EventSource can't set headers). Omit for a
|
|
197
|
+
* loopback/local server, which needs none.
|
|
198
|
+
* @param opts Optional transport overrides (desktop IPC).
|
|
199
|
+
*/
|
|
200
|
+
constructor(baseUrl: string, token?: string, opts?: HttpDataClientOptions);
|
|
201
|
+
/** `fetch` (or the injected transport) with the access token attached (when set). */
|
|
202
|
+
private authFetch;
|
|
203
|
+
/** Lazily create the shared live connection (browser-only). */
|
|
204
|
+
private liveStream;
|
|
205
|
+
listPages(): Promise<PageMeta[]>;
|
|
206
|
+
getPage(id: string): Promise<StoredPage | null>;
|
|
207
|
+
savePage(input: PageInput): Promise<StoredPage>;
|
|
208
|
+
renamePage(id: string, name: string | null): Promise<StoredPage>;
|
|
209
|
+
setPageProperties(id: string, properties: Record<string, unknown>): Promise<StoredPage>;
|
|
210
|
+
listBacklinks(id: string): Promise<PageMeta[]>;
|
|
211
|
+
movePage(id: string, move: {
|
|
212
|
+
parentId: string | null;
|
|
213
|
+
orderedIds: string[];
|
|
214
|
+
}): Promise<StoredPage>;
|
|
215
|
+
deletePage(id: string): Promise<boolean>;
|
|
216
|
+
exportSpace(): Promise<{
|
|
217
|
+
pages: StoredPage[];
|
|
218
|
+
databases: StoredDatabase[];
|
|
219
|
+
}>;
|
|
220
|
+
importSpace(req: ImportRequest): Promise<ImportResult>;
|
|
221
|
+
listTrash(): Promise<PageMeta[]>;
|
|
222
|
+
restorePage(id: string): Promise<StoredPage | null>;
|
|
223
|
+
purgePage(id: string): Promise<boolean>;
|
|
224
|
+
emptyTrash(): Promise<number>;
|
|
225
|
+
subscribePage(id: string, handlers: PageSubscription): () => void;
|
|
226
|
+
subscribePages(onList: (pages: PageMeta[]) => void): () => void;
|
|
227
|
+
createDatabase(input: DatabaseInput): Promise<StoredDatabase>;
|
|
228
|
+
getDatabase(id: string): Promise<StoredDatabase | null>;
|
|
229
|
+
getPageDatabase(pageId: string): Promise<StoredDatabase | null>;
|
|
230
|
+
updateDatabase(id: string, patch: DatabaseUpdate): Promise<StoredDatabase>;
|
|
231
|
+
deleteDatabase(id: string): Promise<boolean>;
|
|
232
|
+
listRows(databaseId: string): Promise<DatabaseRow[]>;
|
|
233
|
+
createRow(databaseId: string, input?: RowInput): Promise<StoredPage>;
|
|
234
|
+
updateRow(databaseId: string, rowId: string, patch: RowUpdate): Promise<DatabaseRow>;
|
|
235
|
+
reorderRows(databaseId: string, orderedIds: string[]): Promise<void>;
|
|
236
|
+
subscribeRows(databaseId: string, onRows: (rows: DatabaseRow[]) => void): () => void;
|
|
237
|
+
listSuggestions(pageId: string, status?: SuggestionStatus): Promise<StoredSuggestion[]>;
|
|
238
|
+
createSuggestion(input: SuggestionInput): Promise<StoredSuggestion>;
|
|
239
|
+
updateSuggestion(id: string, patch: SuggestionUpdate): Promise<StoredSuggestion>;
|
|
240
|
+
deleteSuggestion(id: string): Promise<boolean>;
|
|
241
|
+
listComments(pageId: string): Promise<StoredComment[]>;
|
|
242
|
+
createComment(input: CommentInput): Promise<StoredComment>;
|
|
243
|
+
deleteComment(id: string): Promise<boolean>;
|
|
244
|
+
aiStatus(): Promise<AiStatus>;
|
|
245
|
+
aiSetConfig(config: AiConfig): Promise<AiConfig>;
|
|
246
|
+
aiIndex(): Promise<{
|
|
247
|
+
pages: number;
|
|
248
|
+
chunks: number;
|
|
249
|
+
}>;
|
|
250
|
+
aiSearch(query: string, limit?: number): Promise<AiSearchResponse>;
|
|
251
|
+
aiTasks(goal: string, context?: string): Promise<AiTasksResponse>;
|
|
252
|
+
aiDownloadModel(url?: string): Promise<AiStatus['download']>;
|
|
253
|
+
/**
|
|
254
|
+
* Stream a document completion. `onToken` fires per token; resolves with
|
|
255
|
+
* the full text. Abort via the optional signal.
|
|
256
|
+
*/
|
|
257
|
+
aiComplete(text: string, onToken: (token: string) => void, opts?: {
|
|
258
|
+
instruction?: string;
|
|
259
|
+
signal?: AbortSignal;
|
|
260
|
+
}): Promise<string>;
|
|
261
|
+
/** Stream a raw generation (prompt + optional system). */
|
|
262
|
+
aiGenerate(prompt: string, onToken: (token: string) => void, opts?: {
|
|
263
|
+
system?: string;
|
|
264
|
+
maxTokens?: number;
|
|
265
|
+
signal?: AbortSignal;
|
|
266
|
+
}): Promise<string>;
|
|
267
|
+
listPlugins(): Promise<StoredPlugin[]>;
|
|
268
|
+
installPlugin(pkg: PluginPackage): Promise<StoredPlugin>;
|
|
269
|
+
setPluginEnabled(id: string, enabled: boolean): Promise<StoredPlugin>;
|
|
270
|
+
removePlugin(id: string): Promise<boolean>;
|
|
271
|
+
aiSkills(): Promise<AiSkill[]>;
|
|
272
|
+
aiSaveSkill(skill: AiSkill): Promise<AiSkill>;
|
|
273
|
+
aiDeleteSkill(name: string): Promise<boolean>;
|
|
274
|
+
/** Run the workspace agent, surfacing each streamed step via `onEvent`. */
|
|
275
|
+
agentChat(messages: AgentChatMessage[], onEvent: (event: AgentChatEvent) => void, opts?: AgentChatOptions): Promise<void>;
|
|
276
|
+
/** POST a body and consume the SSE token stream the AI endpoints emit. */
|
|
277
|
+
private aiStream;
|
|
278
|
+
private request;
|
|
279
|
+
}
|