mindoodb-app-sdk 0.0.18 → 0.0.20
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/README.md +355 -4
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/index.d.ts +50 -3
- package/dist/index.js +221 -2
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +3959 -7
- package/dist/{types-jzokkBX5.d.ts → types-CQt2ObMu.d.ts} +149 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -390,11 +390,13 @@ Every `MindooDBAppDocument` snapshot also exposes a `heads?: string[]` field. Th
|
|
|
390
390
|
|
|
391
391
|
`nextCursor` is the latest checkpoint reached by the page. Persist it after each successful call when you are building your own index or sync loop. If there were no changes after the supplied cursor, `nextCursor` is `null`.
|
|
392
392
|
|
|
393
|
-
`documents.update()` accepts
|
|
393
|
+
`documents.update()` accepts five independent kinds of operations on the same call, applied atomically to one new document revision:
|
|
394
394
|
|
|
395
395
|
- `set` -- assign top-level fields (shallow JSON merge).
|
|
396
396
|
- `unset` -- remove top-level fields entirely.
|
|
397
|
-
- `
|
|
397
|
+
- `json` -- a **granular JSON patch** of `set` / `unset` / `listInsert` / `listDelete` operations applied at specific paths inside `document.data`, against a `baseHeads` version. See [Granular JSON edits](#granular-json-edits) below.
|
|
398
|
+
- `text` -- one or more **granular text patches** applied to specific string paths. Each patch carries the `baseHeads` version it was authored against and is merged on the Haven side with any concurrent changes that arrived since. This is the recommended way to edit collaborative text fields like a markdown body. See [Collaborative text editing](#collaborative-text-editing) below.
|
|
399
|
+
- `richText` -- one or more **rich-text span snapshots** for formatted-document fields (Word/.docx style). See [Collaborative rich-text editing](#collaborative-rich-text-editing) below.
|
|
398
400
|
|
|
399
401
|
Prefer sending small, intentional field-level changes instead of rewriting whole documents when only a few fields changed. This keeps MindooDB change tracking more meaningful and helps Automerge produce cleaner merge results.
|
|
400
402
|
|
|
@@ -444,6 +446,85 @@ const pointInTime = await db.documents.getAtTimestamp(
|
|
|
444
446
|
|
|
445
447
|
Historical snapshots include the document data payload as it existed at that revision. When the snapshot has attachments, `attachments` reflects the historical `_attachments` array for that same revision, including the attachment reference that Haven needs to stop at the correct historical `lastChunkId`.
|
|
446
448
|
|
|
449
|
+
### Granular JSON edits
|
|
450
|
+
|
|
451
|
+
When you need to edit nested objects or lists inside a document — not just top-level fields — describe the change as a **granular JSON patch** rather than rewriting the whole document. This is the model behind structured collaborative editing in apps like [`mindoodb-app-teamgrid`](https://github.com/klehmann/mindoodb-app-teamgrid), where two users can insert different rows into the same worksheet on different devices and the merge keeps both sides cleanly.
|
|
452
|
+
|
|
453
|
+
A JSON patch is a bag of four operation kinds applied at specific JSON paths inside `document.data`, all carried with the document version (`baseHeads`) the app was looking at when it composed them:
|
|
454
|
+
|
|
455
|
+
- `set` — assign or replace a value at a path
|
|
456
|
+
- `unset` — remove a key (or list entry, when the host accepts it) at a path
|
|
457
|
+
- `listInsert` — splice values into a list at a position
|
|
458
|
+
- `listDelete` — remove a contiguous run of entries from a list
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
interface MindooDBAppJsonSetPatch {
|
|
462
|
+
path: Array<string | number>;
|
|
463
|
+
value: unknown;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
interface MindooDBAppJsonUnsetPatch {
|
|
467
|
+
path: Array<string | number>;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
interface MindooDBAppJsonListInsertPatch {
|
|
471
|
+
path: Array<string | number>;
|
|
472
|
+
index: number;
|
|
473
|
+
values: unknown[];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
interface MindooDBAppJsonListDeletePatch {
|
|
477
|
+
path: Array<string | number>;
|
|
478
|
+
index: number;
|
|
479
|
+
deleteCount: number;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
interface MindooDBAppJsonPatch {
|
|
483
|
+
baseHeads?: string[];
|
|
484
|
+
set?: MindooDBAppJsonSetPatch[];
|
|
485
|
+
unset?: MindooDBAppJsonUnsetPatch[];
|
|
486
|
+
listInsert?: MindooDBAppJsonListInsertPatch[];
|
|
487
|
+
listDelete?: MindooDBAppJsonListDeletePatch[];
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Send the patch through `documents.update({ json })`:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
const doc = await db.documents.get(docId);
|
|
495
|
+
|
|
496
|
+
await db.documents.update(docId, {
|
|
497
|
+
json: {
|
|
498
|
+
baseHeads: doc!.heads,
|
|
499
|
+
set: [
|
|
500
|
+
{ path: ["worksheets", "ws-1", "title"], value: "Q1 numbers" },
|
|
501
|
+
{
|
|
502
|
+
path: ["worksheets", "ws-1", "cellsByRowCol", "row-3:col-2", "value"],
|
|
503
|
+
value: 42,
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
listInsert: [
|
|
507
|
+
{
|
|
508
|
+
path: ["worksheets", "ws-1", "rowOrder"],
|
|
509
|
+
index: 5,
|
|
510
|
+
values: ["row-new"],
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
listDelete: [
|
|
514
|
+
{
|
|
515
|
+
path: ["worksheets", "ws-1", "tombstoneRows"],
|
|
516
|
+
index: 0,
|
|
517
|
+
deleteCount: 1,
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Haven applies the patch causally at `baseHeads` and merges it with any concurrent changes that arrived since, the same way the text and rich-text APIs do. List inserts authored against the same `baseHeads` interleave cleanly via Automerge's list CRDT instead of overwriting each other — which is exactly what makes two users inserting different rows on the same worksheet at the same time merge into a consistent grid rather than a last-writer-wins.
|
|
525
|
+
|
|
526
|
+
Patch flavors compose. `set`/`unset` (top-level fields), `json`, `text`, and `richText` can all appear in the same `documents.update()` call and are applied atomically to one new document revision. Prefer small, intentional operations over whole-document rewrites so MindooDB change tracking stays meaningful and Automerge produces cleaner merges.
|
|
527
|
+
|
|
447
528
|
### Collaborative text editing
|
|
448
529
|
|
|
449
530
|
MindooDB stores collaborative text fields as Automerge text CRDTs internally, but apps never touch Automerge directly -- the SDK bridge is JSON-only. Instead, your app describes text changes as **granular splice operations** against a known document version, and Haven applies and merges them on its side.
|
|
@@ -579,6 +660,239 @@ Buffer API at a glance:
|
|
|
579
660
|
|
|
580
661
|
The [`mindoodb-app-teamedit`](https://github.com/klehmann/mindoodb-app-teamedit) sample wires `MindooDBTextBuffer` to a Milkdown markdown editor with Save / Refresh / preview pane, and is the recommended starting point for building your own collaborative text app.
|
|
581
662
|
|
|
663
|
+
### Collaborative rich-text editing
|
|
664
|
+
|
|
665
|
+
For structured documents that need formatting — headings, bold/italic runs, lists, tables, fonts, colors — use the **rich-text patch API**. This is the surface behind the Word/.docx mode in [`mindoodb-app-teamedit`](https://github.com/klehmann/mindoodb-app-teamedit), where two users can edit the same document at the same time and Haven merges both formatting and structure on its side via Automerge's rich-text CRDT.
|
|
666
|
+
|
|
667
|
+
Rich-text fields are stored internally as Automerge rich-text, but apps never see Automerge directly. The bridge speaks JSON and represents a rich-text field as an ordered list of **spans** — text runs with marks for inline formatting, plus block markers for paragraphs, headings, lists, and tables:
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
interface MindooDBAppRichTextTextSpan {
|
|
671
|
+
type: "text";
|
|
672
|
+
value: string;
|
|
673
|
+
// Inline marks: bold, italic, underline, font, color, link, ...
|
|
674
|
+
marks?: Record<string, MindooDBAppRichTextMaterializeValue>;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
interface MindooDBAppRichTextBlockSpan {
|
|
678
|
+
type: "block";
|
|
679
|
+
// Block markers: paragraph, heading, list item, table cell, ...
|
|
680
|
+
value: Record<string, MindooDBAppRichTextMaterializeValue>;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
type MindooDBAppRichTextSpan =
|
|
684
|
+
| MindooDBAppRichTextTextSpan
|
|
685
|
+
| MindooDBAppRichTextBlockSpan;
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
#### Reading the current spans
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
const snapshot = await db.documents.getRichText(docId, ["body"]);
|
|
692
|
+
// snapshot = { path: ["body"], heads: ["<commit>"], spans: [...] }
|
|
693
|
+
|
|
694
|
+
editor.loadFromSpans(snapshot.spans);
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
`getRichText(docId, path, options?)` also accepts `{ revisionId }` so you can load the rich-text spans of an exact historical document revision (handy for read-only revision pickers).
|
|
698
|
+
|
|
699
|
+
#### Writing back a span snapshot
|
|
700
|
+
|
|
701
|
+
The simplest write is a full snapshot — replace the field's spans with the editor's current state:
|
|
702
|
+
|
|
703
|
+
```ts
|
|
704
|
+
await db.documents.update(docId, {
|
|
705
|
+
richText: [
|
|
706
|
+
{
|
|
707
|
+
path: ["body"],
|
|
708
|
+
baseHeads: snapshot.heads,
|
|
709
|
+
spans: editor.toSpans(),
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Haven applies the snapshot via Automerge's `updateSpans` and merges it with any concurrent rich-text changes that arrived since `baseHeads`. The optional `updateSpansConfig` field is forwarded to Automerge for editors that need to tune merge behavior at the span level.
|
|
716
|
+
|
|
717
|
+
#### High-level: `createMindooDBRichTextHandle`
|
|
718
|
+
|
|
719
|
+
For editors that emit a complete spans array after each transaction, the SDK ships a buffer helper that mirrors `MindooDBTextBuffer`:
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import { createMindooDBRichTextHandle } from "mindoodb-app-sdk";
|
|
723
|
+
|
|
724
|
+
const doc = await db.documents.get(docId);
|
|
725
|
+
const initial = await db.documents.getRichText(doc!.id, ["body"]);
|
|
726
|
+
|
|
727
|
+
const handle = createMindooDBRichTextHandle({
|
|
728
|
+
database: db,
|
|
729
|
+
document: doc!,
|
|
730
|
+
path: ["body"],
|
|
731
|
+
spans: initial.spans,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
editor.loadFromSpans(handle.spans);
|
|
735
|
+
|
|
736
|
+
// Editor reports a fresh spans array after every change.
|
|
737
|
+
editor.on("change", (spans) => {
|
|
738
|
+
handle.replaceSpans(spans);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// On save (e.g. Cmd+S) flush the local snapshot to Haven.
|
|
742
|
+
async function save() {
|
|
743
|
+
const result = await handle.flush();
|
|
744
|
+
if (result.reconciled) {
|
|
745
|
+
// Haven merged in concurrent rich-text edits; show the canonical merged spans.
|
|
746
|
+
editor.loadFromSpans(result.snapshot.spans);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Optional: poll for remote changes while the editor is idle.
|
|
751
|
+
handle.startPolling(2000);
|
|
752
|
+
handle.on("change", (snapshot) => {
|
|
753
|
+
if (!handle.dirty) editor.loadFromSpans(snapshot.spans);
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
Handle API at a glance:
|
|
758
|
+
|
|
759
|
+
| Member | Description |
|
|
760
|
+
| ------------------------------------ | ----------------------------------------------------------------------------- |
|
|
761
|
+
| `spans` | Current local span snapshot |
|
|
762
|
+
| `dirty` | Whether local edits are buffered |
|
|
763
|
+
| `baseHeads` | Heads the local snapshot was authored against |
|
|
764
|
+
| `replaceSpans(spans)` | Replace the local spans buffer with a fresh snapshot |
|
|
765
|
+
| `refresh()` | Re-read `getRichText` and return the canonical snapshot |
|
|
766
|
+
| `pollOnce()` | Re-read and reconcile if the document advanced and there are no local edits |
|
|
767
|
+
| `startPolling(ms)` / `stopPolling()` | Poll `pollOnce` on a timer |
|
|
768
|
+
| `flush(updateSpansConfig?)` | Send pending spans to Haven; resolves to `{ document, snapshot, reconciled }` |
|
|
769
|
+
| `on/off("change", listener)` | Subscribe to local or reconciled-change events |
|
|
770
|
+
|
|
771
|
+
Like the text buffer, the rich-text handle always sends the original `baseHeads` captured when it was created or last reconciled, so two windows editing the same document at the same heads merge correctly without overwriting each other.
|
|
772
|
+
|
|
773
|
+
#### End-to-end sample
|
|
774
|
+
|
|
775
|
+
The Word/.docx mode in [`mindoodb-app-teamedit`](https://github.com/klehmann/mindoodb-app-teamedit) is the **only supported in-repo use case** for the Automerge binary sync API below — it needs a local replica and binary flush because `set`/`unset` and the JSON patch APIs are not sufficient for that editor. For span-only rich-text apps, use `createMindooDBRichTextHandle` instead.
|
|
776
|
+
|
|
777
|
+
### Automerge binary sync (advanced)
|
|
778
|
+
|
|
779
|
+
> **Avoid these APIs unless you have no alternative.** For normal app editing, use `documents.update({ set })`, `documents.update({ unset })`, and the JSON patch operations (`text`, `richText`, `json`) documented above. `getAutomergeSnapshot` and `applyAutomergeChanges` were added as an **escape hatch** for the one case where those APIs are not enough: TeamEdit's Word/.docx mode, which hosts a local `@automerge/automerge` replica and flushes raw change bytes on save. Do not adopt binary sync for new features unless you have the same constraint — you take on an Automerge dependency, manual replica lifecycle, and a much harder integration path.
|
|
780
|
+
|
|
781
|
+
If you must use binary sync, Haven decrypts the canonical document, merges incoming change batches with `Automerge.applyChanges`, and persists the result. Your app never sends encrypted blobs or sync metadata; it only sends the same binary change format Automerge produces locally.
|
|
782
|
+
|
|
783
|
+
Typical flow:
|
|
784
|
+
|
|
785
|
+
```
|
|
786
|
+
┌────────────────────────────────────────────────────┐
|
|
787
|
+
open ──► │ getAutomergeSnapshot → load local replica │
|
|
788
|
+
└─────────────────────────┬──────────────────────────┘
|
|
789
|
+
│
|
|
790
|
+
edit ──► │ Local Automerge doc (or span edits applied locally) │
|
|
791
|
+
└─────────────────────────┬──────────────────────────┘
|
|
792
|
+
│ getChangesSince(baseHeads)
|
|
793
|
+
▼
|
|
794
|
+
┌────────────────────────────────────────────────────┐
|
|
795
|
+
save ──► │ applyAutomergeChanges({ baseHeads, replicaHeads, │
|
|
796
|
+
│ changes }) │
|
|
797
|
+
└─────────────────────────┬──────────────────────────┘
|
|
798
|
+
│ Haven merges + persists
|
|
799
|
+
▼
|
|
800
|
+
┌────────────────────────────────────────────────────┐
|
|
801
|
+
│ Apply result.changesSince locally (or skip if empty) │
|
|
802
|
+
└────────────────────────────────────────────────────┘
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
Bridge RPCs: `documents.automerge.getSnapshot` and `documents.automerge.applyChanges`.
|
|
806
|
+
|
|
807
|
+
#### `getAutomergeSnapshot`
|
|
808
|
+
|
|
809
|
+
Export the full internal Automerge document as a binary snapshot plus the current document heads:
|
|
810
|
+
|
|
811
|
+
```ts
|
|
812
|
+
const snapshot = await db.documents.getAutomergeSnapshot(docId);
|
|
813
|
+
// snapshot = { binary: Uint8Array, heads: string[] }
|
|
814
|
+
|
|
815
|
+
const localDoc = Automerge.load(snapshot.binary);
|
|
816
|
+
const baseHeads = [...snapshot.heads];
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Options:
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
interface MindooDBAppAutomergeGetOptions {
|
|
823
|
+
revisionId?: MindooDBAppDocumentRevisionId; // historical read via documents.listHistory()
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Only call this when binary sync is unavoidable — e.g. opening a local replica, refreshing after `applyAutomergeChanges`, or read-only historical preview at a `revisionId`. For normal reads, use `getRichText` or `documents.get`.
|
|
828
|
+
|
|
829
|
+
#### `applyAutomergeChanges`
|
|
830
|
+
|
|
831
|
+
Merge a batch of raw Automerge change bytes into the canonical document on Haven:
|
|
832
|
+
|
|
833
|
+
```ts
|
|
834
|
+
const changes = Automerge.getChangesSince(localDoc, baseHeads as Automerge.Heads);
|
|
835
|
+
const replicaHeads = Automerge.getHeads(localDoc);
|
|
836
|
+
|
|
837
|
+
const result = await db.documents.applyAutomergeChanges(docId, {
|
|
838
|
+
baseHeads, // optional metadata: heads when the local batch was authored
|
|
839
|
+
replicaHeads, // local heads after authoring; enables incremental catch-up in the response
|
|
840
|
+
changes: changes.map((change) => new Uint8Array(change)),
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// result.document — updated MindooDBAppDocument snapshot (includes merged data + heads)
|
|
844
|
+
// result.heads — new Automerge heads after the merge
|
|
845
|
+
// result.changesSince — incremental bytes to reconcile the local replica (when replicaHeads was sent)
|
|
846
|
+
if (result.changesSince?.changes.length) {
|
|
847
|
+
[localDoc] = Automerge.applyChanges(
|
|
848
|
+
localDoc,
|
|
849
|
+
result.changesSince.changes.map((change) => new Uint8Array(change)),
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
baseHeads = [...result.heads];
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
Patch and result types:
|
|
856
|
+
|
|
857
|
+
```ts
|
|
858
|
+
interface MindooDBAppAutomergeChangesPatch {
|
|
859
|
+
baseHeads?: string[]; // correlation only; merge is not rejected when Haven moved on
|
|
860
|
+
replicaHeads?: string[]; // local heads after authoring `changes`; enables `changesSince` in the response
|
|
861
|
+
changes: Uint8Array[]; // from Automerge.getChangesSince (or equivalent)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
interface MindooDBAppAutomergeChangesSince {
|
|
865
|
+
sinceHeads: string[]; // echo of `replicaHeads`
|
|
866
|
+
changes: Uint8Array[]; // apply locally with Automerge.applyChanges
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
interface MindooDBAppAutomergePatchResult {
|
|
870
|
+
document: MindooDBAppDocument;
|
|
871
|
+
heads: string[];
|
|
872
|
+
changesSince?: MindooDBAppAutomergeChangesSince;
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Haven applies `changes` with `Automerge.applyChanges` against its **current** document, so concurrent edits from other clients are merged by Automerge's CRDT logic rather than rejected. `baseHeads` is optional logging metadata — it does not gate the merge.
|
|
877
|
+
|
|
878
|
+
When you include `replicaHeads` (the local replica's heads after authoring your batch), the response includes `changesSince`: the incremental change bytes your replica still needs after the merge. Apply them locally with `Automerge.applyChanges` and set `baseHeads = result.heads`. When there was no concurrent edit, `changesSince.changes` is empty and your local doc is already up to date — no full snapshot download needed. Omit `replicaHeads` only if you plan to reload via `getAutomergeSnapshot` instead.
|
|
879
|
+
|
|
880
|
+
#### When to use binary sync vs JSON patches
|
|
881
|
+
|
|
882
|
+
| Approach | App sends | When to use |
|
|
883
|
+
| -------- | --------- | ----------- |
|
|
884
|
+
| `documents.update({ set })` / `{ unset }` | Field paths | **Default** for structured document data |
|
|
885
|
+
| `documents.update({ richText })` | JSON span snapshots | **Default** for rich-text fields |
|
|
886
|
+
| `documents.update({ text })` | Text splices | Plain markdown / string fields |
|
|
887
|
+
| `documents.update({ json })` | JSON Patch (RFC 6902) | Arbitrary nested JSON paths |
|
|
888
|
+
| `applyAutomergeChanges` | Binary change bytes | **Last resort only** — TeamEdit Word/.docx with a local Automerge replica |
|
|
889
|
+
|
|
890
|
+
Requires the `update` capability (same as other write operations). For historical reads, prefer `getRichText(..., { revisionId })`; only use `getAutomergeSnapshot(..., { revisionId })` when you truly need the full binary at a revision.
|
|
891
|
+
|
|
892
|
+
#### Reference implementation (not a template)
|
|
893
|
+
|
|
894
|
+
TeamEdit's Word/.docx mode is why these RPCs exist — see `wordAutomergeHandle.ts` in [`mindoodb-app-teamedit`](https://github.com/klehmann/mindoodb-app-teamedit). Treat it as a reference for an unsupported edge case, not as a pattern to copy into new apps.
|
|
895
|
+
|
|
582
896
|
### Incremental sync
|
|
583
897
|
|
|
584
898
|
The changefeed-backed `documents.list()` API is also the right primitive for app-side indexes, search, and other derived caches.
|
|
@@ -675,12 +989,41 @@ const scan = await db.attachments.scan(docId, {
|
|
|
675
989
|
preset: "a4-portrait",
|
|
676
990
|
mimeType: "application/pdf",
|
|
677
991
|
});
|
|
992
|
+
// scan = { ok: true, attachment: { attachmentId, fileName, mimeType, size } }
|
|
993
|
+
// or { ok: false } if the user cancelled the scanner
|
|
994
|
+
// or rejected with an error if the host has no scanner UI
|
|
678
995
|
|
|
679
996
|
// Remove
|
|
680
997
|
await db.attachments.remove(docId, "old-file.txt");
|
|
681
998
|
```
|
|
682
999
|
|
|
683
|
-
`attachments.scan()` asks Haven to open its document scanner UI. Haven handles camera
|
|
1000
|
+
`attachments.scan()` asks Haven to open its document scanner UI on top of your app. Haven handles camera or file selection, runs edge detection through a slim on-device OpenCV WebAssembly pipeline, lets the user fine-tune the corners, applies the perspective correction to the chosen output size, and writes the resulting bytes as a normal MindooDoc attachment via the same path `openWriteStream` would have taken. The scanner runs entirely locally — the picked image and the perspective-corrected result never leave the browser tab.
|
|
1001
|
+
|
|
1002
|
+
Options shape:
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
type MindooDBAppDocumentScanPreset =
|
|
1006
|
+
| "auto" // free-form, keep the detected aspect ratio
|
|
1007
|
+
| "a4-portrait"
|
|
1008
|
+
| "a4-landscape"
|
|
1009
|
+
| "letter-portrait"
|
|
1010
|
+
| "letter-landscape";
|
|
1011
|
+
|
|
1012
|
+
interface MindooDBAppScanAttachmentOptions {
|
|
1013
|
+
defaultFileName?: string; // suggested filename in the dialog
|
|
1014
|
+
preset?: MindooDBAppDocumentScanPreset; // output page geometry
|
|
1015
|
+
mimeType?: "image/jpeg" | "image/png" | "application/pdf"; // output format
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
interface MindooDBAppScanAttachmentResult {
|
|
1019
|
+
ok: boolean;
|
|
1020
|
+
attachment?: MindooDBAppAttachmentInfo | null;
|
|
1021
|
+
}
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
Apps need both the `attachments` and `update` capabilities for this method, because the scanner writes the resulting bytes back to the document. Hosts that do not provide scanner UI may reject this method or return `{ ok: false }`.
|
|
1025
|
+
|
|
1026
|
+
The same scanner is also reachable as a top-level **Quick Scan** entry in Haven's sidebar. There it runs detached from any document — the user can download the resulting file or hand it to the operating system's share sheet, but the "Add to document" action is hidden because there is no document context. Apps do not need to do anything to support this; it is part of Haven, not the SDK.
|
|
684
1027
|
|
|
685
1028
|
For historical reads, pass the same `revisionId` you received from `documents.listHistory()` or from a `MindooDBAppHistoricalDocument`. This tells Haven to resolve the attachment from that revision's document payload instead of the latest document state:
|
|
686
1029
|
|
|
@@ -983,6 +1326,9 @@ Connect options: `launchId?`, `targetOrigin?`, `connectTimeoutMs?`.
|
|
|
983
1326
|
| ---------------------------------- | -------------------------------------------- |
|
|
984
1327
|
| `list(query?)` | `Promise<MindooDBAppDocumentListResult>` |
|
|
985
1328
|
| `get(docId)` | `Promise<MindooDBAppDocument \| null>` |
|
|
1329
|
+
| `getRichText(docId, path, opts?)` | `Promise<MindooDBAppRichTextSnapshot>` |
|
|
1330
|
+
| `getAutomergeSnapshot(docId, opts?)` | `Promise<MindooDBAppAutomergeSnapshot>` |
|
|
1331
|
+
| `applyAutomergeChanges(docId, patch)` | `Promise<MindooDBAppAutomergePatchResult>` |
|
|
986
1332
|
| `create(input)` | `Promise<MindooDBAppDocument>` |
|
|
987
1333
|
| `update(docId, patch)` | `Promise<MindooDBAppDocument>` |
|
|
988
1334
|
| `delete(docId)` | `Promise<{ ok: true }>` |
|
|
@@ -991,6 +1337,8 @@ Connect options: `launchId?`, `targetOrigin?`, `connectTimeoutMs?`.
|
|
|
991
1337
|
| `getAtTimestamp(docId, timestamp)` | `Promise<MindooDBAppHistoricalDocument>` |
|
|
992
1338
|
| `getAtRevision(docId, revisionId)` | `Promise<MindooDBAppHistoricalDocument>` |
|
|
993
1339
|
|
|
1340
|
+
`getAutomergeSnapshot` and `applyAutomergeChanges` are **discouraged** escape-hatch APIs for binary Automerge sync. Prefer `set`/`unset` and JSON patches (`text`, `richText`, `json`) instead. See [Automerge binary sync (advanced)](#automerge-binary-sync-advanced).
|
|
1341
|
+
|
|
994
1342
|
`list(query?)` accepts the changefeed query options documented above. The `cursor` value is an opaque checkpoint string managed by Haven and should be stored and passed back unchanged.
|
|
995
1343
|
|
|
996
1344
|
For `create(input)`, use `MindooDBAppCreateDocumentInput` with shape `{ set: Record<string, unknown>; decryptionKeyId?: string; id?: string }`. The optional `id` lets the app pick the document id instead of letting MindooDB generate a UUID7; when provided it must match `/^[A-Za-z][A-Za-z0-9_]*$/` and existing live documents with that id are returned as-is (idempotent create-if-missing). If an existing custom-id document is deleted, `create({ id })` undeletes it and preserves the previous document body. Documents created with the same custom id on different replicas converge correctly when synced.
|
|
@@ -1003,11 +1351,13 @@ For `update(docId, patch)`, use `MindooDBAppUpdateDocumentInput`:
|
|
|
1003
1351
|
interface MindooDBAppUpdateDocumentInput {
|
|
1004
1352
|
set?: Record<string, unknown>; // shallow assign top-level fields
|
|
1005
1353
|
unset?: string[]; // remove top-level fields entirely
|
|
1354
|
+
json?: MindooDBAppJsonPatch; // granular structured edits at JSON paths
|
|
1006
1355
|
text?: MindooDBAppTextPatch[]; // granular collaborative text edits
|
|
1356
|
+
richText?: MindooDBAppRichTextPatch[]; // collaborative rich-text span snapshots
|
|
1007
1357
|
}
|
|
1008
1358
|
```
|
|
1009
1359
|
|
|
1010
|
-
`set` and `unset` apply to top-level fields only. `text` patches operate
|
|
1360
|
+
`set` and `unset` apply to top-level fields only. `json`, `text`, and `richText` patches operate at any JSON path inside `document.data` and are merged on the Haven side using the document's Automerge heads. See [Granular JSON edits](#granular-json-edits), [Collaborative text editing](#collaborative-text-editing), and [Collaborative rich-text editing](#collaborative-rich-text-editing).
|
|
1011
1361
|
|
|
1012
1362
|
### MindooDBAppAttachmentApi
|
|
1013
1363
|
|
|
@@ -1070,6 +1420,7 @@ interface MindooDBAppUpdateDocumentInput {
|
|
|
1070
1420
|
| ------------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
1071
1421
|
| `createMindooDBAppBridge()` | Create the bridge object used to connect to Haven |
|
|
1072
1422
|
| `createMindooDBTextBuffer(options)` | Create an editor-friendly buffer for granular collaborative text edits on a document field |
|
|
1423
|
+
| `createMindooDBRichTextHandle(options)` | Create an editor-friendly handle for collaborative rich-text span snapshots on a document field |
|
|
1073
1424
|
| `canPreviewAttachment(fileName, mimeType)` | Check if Haven can preview a file (returns mode or `null`) |
|
|
1074
1425
|
| `createViewLanguage<T>()` | Create a typed expression builder for view definitions |
|
|
1075
1426
|
| `abbreviateCanonicalName(value)` | Convert a canonical Notes-style name like `cn=Jane/ou=Dev/o=Mindoo` to `Jane/Dev/Mindoo` |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
__require,
|
|
35
|
+
__commonJS,
|
|
36
|
+
__toESM
|
|
37
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppBridge, b as MindooDBAppDatabase, c as MindooDBAppDocument } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppBridge, b as MindooDBAppDatabase, c as MindooDBAppDocument, d as MindooDBAppRichTextSpan, e as MindooDBAppRichTextSnapshot } from './types-CQt2ObMu.js';
|
|
2
|
+
export { f as MindooDBAppAttachmentApi, g as MindooDBAppAttachmentChunk, h as MindooDBAppAttachmentInfo, i as MindooDBAppAttachmentPreviewOptions, j as MindooDBAppAttachmentPreviewSession, k as MindooDBAppAutomergeChangesPatch, l as MindooDBAppAutomergeChangesSince, m as MindooDBAppAutomergeGetOptions, n as MindooDBAppAutomergePatchResult, o as MindooDBAppAutomergeSnapshot, p as MindooDBAppBridgeConnectMessage, q as MindooDBAppBridgeConnectOptions, r as MindooDBAppBridgeConnectedMessage, s as MindooDBAppBridgeErrorPayload, t as MindooDBAppBridgeHandshakeErrorMessage, u as MindooDBAppBridgePortMessage, v as MindooDBAppBridgeRpcError, w as MindooDBAppBridgeRpcMessage, x as MindooDBAppBridgeRpcRequest, y as MindooDBAppBridgeRpcSuccess, z as MindooDBAppBridgeStreamAbortRequest, A as MindooDBAppBridgeStreamAck, B as MindooDBAppBridgeStreamCloseRequest, C as MindooDBAppBridgeStreamError, D as MindooDBAppBridgeStreamMessage, E as MindooDBAppBridgeStreamOpenResult, F as MindooDBAppBridgeStreamReadRequest, G as MindooDBAppBridgeStreamWriteRequest, H as MindooDBAppBridgeThemeChangedMessage, I as MindooDBAppBridgeUiPreferencesChangedMessage, J as MindooDBAppBridgeViewportChangedMessage, K as MindooDBAppCapability, L as MindooDBAppConfiguredViewCategorizationStyle, N as MindooDBAppConfiguredViewColumn, O as MindooDBAppConfiguredViewColumnRole, P as MindooDBAppConfiguredViewExpressionMode, Q as MindooDBAppConfiguredViewFieldExpression, R as MindooDBAppConfiguredViewFilterDefinition, S as MindooDBAppConfiguredViewFilterMode, T as MindooDBAppConfiguredViewFilterRule, U as MindooDBAppConfiguredViewFormulaExpression, V as MindooDBAppConfiguredViewFormulaFilterDefinition, W as MindooDBAppConfiguredViewPreviewMode, X as MindooDBAppConfiguredViewRuleFilterDefinition, Y as MindooDBAppConfiguredViewRuleMatchMode, Z as MindooDBAppConfiguredViewRuleOperator, _ as MindooDBAppConfiguredViewValueExpression, $ as MindooDBAppCreateDocumentInput, a0 as MindooDBAppCreateViewInput, a1 as MindooDBAppCreateViewNavigatorInput, a2 as MindooDBAppDatabaseInfo, a3 as MindooDBAppDocumentApi, a4 as MindooDBAppDocumentHeadCursorResult, a5 as MindooDBAppDocumentHistoryEntry, a6 as MindooDBAppDocumentListQuery, a7 as MindooDBAppDocumentListResult, a8 as MindooDBAppDocumentRevisionId, a9 as MindooDBAppDocumentScanPreset, aa as MindooDBAppDocumentSummary, ab as MindooDBAppHistoricalDocument, ac as MindooDBAppHistoryQuery, ad as MindooDBAppHostTheme, ae as MindooDBAppJsonListDeletePatch, af as MindooDBAppJsonListInsertPatch, ag as MindooDBAppJsonPatch, ah as MindooDBAppJsonSetPatch, ai as MindooDBAppJsonTextMarkPatch, aj as MindooDBAppJsonTextSplicePatch, ak as MindooDBAppJsonTextUnmarkPatch, al as MindooDBAppJsonUnsetPatch, am as MindooDBAppLaunchContext, an as MindooDBAppMenuAnchor, ao as MindooDBAppMenuApi, ap as MindooDBAppMenuCommandItem, aq as MindooDBAppMenuDismissReason, ar as MindooDBAppMenuDismissedResult, as as MindooDBAppMenuItem, at as MindooDBAppMenuKind, au as MindooDBAppMenuPlacement, av as MindooDBAppMenuPointAnchor, aw as MindooDBAppMenuRect, ax as MindooDBAppMenuRectAnchor, ay as MindooDBAppMenuSelectionResult, az as MindooDBAppMenuSeparatorItem, aA as MindooDBAppReadableAttachmentStream, aB as MindooDBAppResolvedViewDefinition, aC as MindooDBAppResolvedViewSource, aD as MindooDBAppRichTextBlockSpan, aE as MindooDBAppRichTextGetOptions, aF as MindooDBAppRichTextImmutableString, aG as MindooDBAppRichTextMarkRange, aH as MindooDBAppRichTextMaterializeValue, aI as MindooDBAppRichTextPatch, aJ as MindooDBAppRichTextScalar, aK as MindooDBAppRichTextSpliceStep, aL as MindooDBAppRichTextStep, aM as MindooDBAppRichTextStepPatch, aN as MindooDBAppRichTextTextSpan, aO as MindooDBAppRuntime, aP as MindooDBAppScanAttachmentOptions, aQ as MindooDBAppScanAttachmentResult, aR as MindooDBAppScopedDocId, aS as MindooDBAppScopedDocumentSummary, aT as MindooDBAppSession, aU as MindooDBAppShowMenuInput, aV as MindooDBAppShowMenuResult, aW as MindooDBAppTextEdit, aX as MindooDBAppTextPatch, aY as MindooDBAppThemeMode, aZ as MindooDBAppUiPreferences, a_ as MindooDBAppUpdateDocumentInput, a$ as MindooDBAppViewCursorDocumentListResult, b0 as MindooDBAppViewCursorResult, b1 as MindooDBAppViewEntry, b2 as MindooDBAppViewEntryKind, b3 as MindooDBAppViewNavigator, b4 as MindooDBAppViewNavigatorExpansionState, b5 as MindooDBAppViewNavigatorOpenOptions, b6 as MindooDBAppViewNavigatorPageOptions, b7 as MindooDBAppViewNavigatorPageResult, b8 as MindooDBAppViewNavigatorRangeQuery, b9 as MindooDBAppViewNavigatorSelectionState, ba as MindooDBAppViewport, bb as MindooDBAppWritableAttachmentStream } from './types-CQt2ObMu.js';
|
|
3
3
|
export { MindooDBAppBooleanExpression, MindooDBAppExpression, MindooDBAppFilterDefinition, MindooDBAppViewCategoryChildrenPageRequest, MindooDBAppViewColumn, MindooDBAppViewColumnRole, MindooDBAppViewDefinition, MindooDBAppViewExpansionState, MindooDBAppViewExpressionBase, MindooDBAppViewExpressionDatePart, MindooDBAppViewExpressionOperation, MindooDBAppViewFieldExpression, MindooDBAppViewIfExpression, MindooDBAppViewLetExpression, MindooDBAppViewLiteralExpression, MindooDBAppViewLookupByPath, MindooDBAppViewOperationExpression, MindooDBAppViewOriginExpression, MindooDBAppViewPageRequest, MindooDBAppViewPageResult, MindooDBAppViewRow, MindooDBAppViewSortDirection, MindooDBAppViewTotalMode, MindooDBAppViewValueExpressionRef, MindooDBAppViewVariableExpression, createViewLanguage } from 'mindoodb-view-language';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -37,6 +37,53 @@ declare function expandAbbreviatedName(value: string): string;
|
|
|
37
37
|
*/
|
|
38
38
|
declare function createMindooDBAppBridge(): MindooDBAppBridge;
|
|
39
39
|
|
|
40
|
+
interface CreateMindooDBRichTextHandleOptions {
|
|
41
|
+
database: MindooDBAppDatabase;
|
|
42
|
+
document: MindooDBAppDocument;
|
|
43
|
+
path: Array<string | number>;
|
|
44
|
+
spans?: MindooDBAppRichTextSpan[];
|
|
45
|
+
pollIntervalMs?: number;
|
|
46
|
+
}
|
|
47
|
+
interface MindooDBRichTextHandleFlushResult {
|
|
48
|
+
document: MindooDBAppDocument;
|
|
49
|
+
snapshot: MindooDBAppRichTextSnapshot;
|
|
50
|
+
reconciled: boolean;
|
|
51
|
+
}
|
|
52
|
+
type MindooDBRichTextHandleChangeListener = (snapshot: MindooDBAppRichTextSnapshot) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Local rich-text span buffer for SDK apps.
|
|
55
|
+
*
|
|
56
|
+
* Mirrors `MindooDBTextBuffer`: track edits against a base snapshot, then flush
|
|
57
|
+
* with causal `baseHeads`. Text edits prefer positional `richTextSteps` so
|
|
58
|
+
* concurrent saves merge like plain-text splices; formatting-only or structural
|
|
59
|
+
* changes fall back to full `richText` span snapshots.
|
|
60
|
+
*/
|
|
61
|
+
declare class MindooDBRichTextHandle {
|
|
62
|
+
private readonly options;
|
|
63
|
+
private spansInternal;
|
|
64
|
+
private baseMaterializedText;
|
|
65
|
+
private heads;
|
|
66
|
+
private dirtyInternal;
|
|
67
|
+
private listeners;
|
|
68
|
+
private pollTimer;
|
|
69
|
+
constructor(options: CreateMindooDBRichTextHandleOptions);
|
|
70
|
+
get spans(): MindooDBAppRichTextSpan[];
|
|
71
|
+
get dirty(): boolean;
|
|
72
|
+
get baseHeads(): string[];
|
|
73
|
+
on(event: "change", listener: MindooDBRichTextHandleChangeListener): void;
|
|
74
|
+
off(event: "change", listener: MindooDBRichTextHandleChangeListener): void;
|
|
75
|
+
replaceSpans(spans: MindooDBAppRichTextSpan[]): void;
|
|
76
|
+
startPolling(intervalMs?: number): void;
|
|
77
|
+
stopPolling(): void;
|
|
78
|
+
reconcile(document: MindooDBAppDocument, snapshot: MindooDBAppRichTextSnapshot): boolean;
|
|
79
|
+
refresh(): Promise<MindooDBAppRichTextSnapshot>;
|
|
80
|
+
pollOnce(): Promise<boolean>;
|
|
81
|
+
flush(updateSpansConfig?: Record<string, unknown>): Promise<MindooDBRichTextHandleFlushResult>;
|
|
82
|
+
private flushTextSteps;
|
|
83
|
+
private emitChange;
|
|
84
|
+
}
|
|
85
|
+
declare function createMindooDBRichTextHandle(options: CreateMindooDBRichTextHandleOptions): MindooDBRichTextHandle;
|
|
86
|
+
|
|
40
87
|
/**
|
|
41
88
|
* Options used to create a text buffer for one string field in a MindooDB
|
|
42
89
|
* document.
|
|
@@ -130,4 +177,4 @@ declare class MindooDBTextBuffer {
|
|
|
130
177
|
/** Create a local text buffer for one string field in a document. */
|
|
131
178
|
declare function createMindooDBTextBuffer(options: CreateMindooDBTextBufferOptions): MindooDBTextBuffer;
|
|
132
179
|
|
|
133
|
-
export { type CreateMindooDBTextBufferOptions, MindooDBAppAttachmentPreviewMode, MindooDBAppBridge, MindooDBAppDatabase, MindooDBAppDocument, MindooDBTextBuffer, type MindooDBTextBufferFlushResult, abbreviateCanonicalName, canPreviewAttachment, createMindooDBAppBridge, createMindooDBTextBuffer, expandAbbreviatedName };
|
|
180
|
+
export { type CreateMindooDBRichTextHandleOptions, type CreateMindooDBTextBufferOptions, MindooDBAppAttachmentPreviewMode, MindooDBAppBridge, MindooDBAppDatabase, MindooDBAppDocument, MindooDBAppRichTextSnapshot, MindooDBAppRichTextSpan, MindooDBRichTextHandle, type MindooDBRichTextHandleChangeListener, type MindooDBRichTextHandleFlushResult, MindooDBTextBuffer, type MindooDBTextBufferFlushResult, abbreviateCanonicalName, canPreviewAttachment, createMindooDBAppBridge, createMindooDBRichTextHandle, createMindooDBTextBuffer, expandAbbreviatedName };
|