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 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 three independent operations on the same call:
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
- - `text` -- one or more **granular text patches** applied to specific JSON 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.
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/file selection, edge detection, manual corner correction, output sizing, and filename confirmation, then writes the resulting image as a normal attachment. Hosts that do not provide scanner UI may reject this method or return `{ ok: false }`.
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 on string fields at any JSON path inside `document.data` and are merged on the Haven side using the document's Automerge heads. See [Collaborative text editing](#collaborative-text-editing).
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-jzokkBX5.js';
2
- export { d as MindooDBAppAttachmentApi, e as MindooDBAppAttachmentChunk, f as MindooDBAppAttachmentInfo, g as MindooDBAppAttachmentPreviewOptions, h as MindooDBAppAttachmentPreviewSession, i as MindooDBAppBridgeConnectMessage, j as MindooDBAppBridgeConnectOptions, k as MindooDBAppBridgeConnectedMessage, l as MindooDBAppBridgeErrorPayload, m as MindooDBAppBridgeHandshakeErrorMessage, n as MindooDBAppBridgePortMessage, o as MindooDBAppBridgeRpcError, p as MindooDBAppBridgeRpcMessage, q as MindooDBAppBridgeRpcRequest, r as MindooDBAppBridgeRpcSuccess, s as MindooDBAppBridgeStreamAbortRequest, t as MindooDBAppBridgeStreamAck, u as MindooDBAppBridgeStreamCloseRequest, v as MindooDBAppBridgeStreamError, w as MindooDBAppBridgeStreamMessage, x as MindooDBAppBridgeStreamOpenResult, y as MindooDBAppBridgeStreamReadRequest, z as MindooDBAppBridgeStreamWriteRequest, A as MindooDBAppBridgeThemeChangedMessage, B as MindooDBAppBridgeUiPreferencesChangedMessage, C as MindooDBAppBridgeViewportChangedMessage, D as MindooDBAppCapability, E as MindooDBAppConfiguredViewCategorizationStyle, F as MindooDBAppConfiguredViewColumn, G as MindooDBAppConfiguredViewColumnRole, H as MindooDBAppConfiguredViewExpressionMode, I as MindooDBAppConfiguredViewFieldExpression, J as MindooDBAppConfiguredViewFilterDefinition, K as MindooDBAppConfiguredViewFilterMode, L as MindooDBAppConfiguredViewFilterRule, N as MindooDBAppConfiguredViewFormulaExpression, O as MindooDBAppConfiguredViewFormulaFilterDefinition, P as MindooDBAppConfiguredViewPreviewMode, Q as MindooDBAppConfiguredViewRuleFilterDefinition, R as MindooDBAppConfiguredViewRuleMatchMode, S as MindooDBAppConfiguredViewRuleOperator, T as MindooDBAppConfiguredViewValueExpression, U as MindooDBAppCreateDocumentInput, V as MindooDBAppCreateViewInput, W as MindooDBAppCreateViewNavigatorInput, X as MindooDBAppDatabaseInfo, Y as MindooDBAppDocumentApi, Z as MindooDBAppDocumentHeadCursorResult, _ as MindooDBAppDocumentHistoryEntry, $ as MindooDBAppDocumentListQuery, a0 as MindooDBAppDocumentListResult, a1 as MindooDBAppDocumentRevisionId, a2 as MindooDBAppDocumentScanPreset, a3 as MindooDBAppDocumentSummary, a4 as MindooDBAppHistoricalDocument, a5 as MindooDBAppHistoryQuery, a6 as MindooDBAppHostTheme, a7 as MindooDBAppJsonListDeletePatch, a8 as MindooDBAppJsonListInsertPatch, a9 as MindooDBAppJsonPatch, aa as MindooDBAppJsonSetPatch, ab as MindooDBAppJsonUnsetPatch, ac as MindooDBAppLaunchContext, ad as MindooDBAppMenuAnchor, ae as MindooDBAppMenuApi, af as MindooDBAppMenuCommandItem, ag as MindooDBAppMenuDismissReason, ah as MindooDBAppMenuDismissedResult, ai as MindooDBAppMenuItem, aj as MindooDBAppMenuKind, ak as MindooDBAppMenuPlacement, al as MindooDBAppMenuPointAnchor, am as MindooDBAppMenuRect, an as MindooDBAppMenuRectAnchor, ao as MindooDBAppMenuSelectionResult, ap as MindooDBAppMenuSeparatorItem, aq as MindooDBAppReadableAttachmentStream, ar as MindooDBAppResolvedViewDefinition, as as MindooDBAppResolvedViewSource, at as MindooDBAppRuntime, au as MindooDBAppScanAttachmentOptions, av as MindooDBAppScanAttachmentResult, aw as MindooDBAppScopedDocId, ax as MindooDBAppScopedDocumentSummary, ay as MindooDBAppSession, az as MindooDBAppShowMenuInput, aA as MindooDBAppShowMenuResult, aB as MindooDBAppTextEdit, aC as MindooDBAppTextPatch, aD as MindooDBAppThemeMode, aE as MindooDBAppUiPreferences, aF as MindooDBAppUpdateDocumentInput, aG as MindooDBAppViewCursorDocumentListResult, aH as MindooDBAppViewCursorResult, aI as MindooDBAppViewEntry, aJ as MindooDBAppViewEntryKind, aK as MindooDBAppViewNavigator, aL as MindooDBAppViewNavigatorExpansionState, aM as MindooDBAppViewNavigatorOpenOptions, aN as MindooDBAppViewNavigatorPageOptions, aO as MindooDBAppViewNavigatorPageResult, aP as MindooDBAppViewNavigatorRangeQuery, aQ as MindooDBAppViewNavigatorSelectionState, aR as MindooDBAppViewport, aS as MindooDBAppWritableAttachmentStream } from './types-jzokkBX5.js';
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 };