mindoodb-app-sdk 0.0.19 → 0.0.26
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 +253 -12
- package/TESTING.md +37 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +270 -8
- package/dist/testing/index.d.ts +23 -1
- package/dist/testing/index.js +501 -3
- package/dist/types-B6vEy1VC.d.ts +1565 -0
- package/package.json +13 -3
- package/dist/types-nFt-aryL.d.ts +0 -780
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# mindoodb-app-sdk
|
|
2
2
|
|
|
3
|
-
Build apps that run inside **MindooDB Haven** -- the browser-based workspace for end-to-end encrypted,
|
|
3
|
+
Build apps that run inside **MindooDB Haven** -- the browser-based workspace for end-to-end encrypted, local-first data.
|
|
4
4
|
|
|
5
5
|
`mindoodb-app-sdk` is the TypeScript SDK that connects your web application to Haven through a secure message bridge. Your app runs in a sandboxed iframe (or a separate browser window) and gains access to databases, documents, attachments, virtual views, and live host events -- all without ever touching encryption keys or raw sync state.
|
|
6
6
|
|
|
7
7
|
## What is a MindooDB App?
|
|
8
8
|
|
|
9
|
-
[MindooDB](https://mindoodb.com) is an **end-to-end encrypted,
|
|
9
|
+
[MindooDB](https://mindoodb.com) is an **end-to-end encrypted, local-first sync database**. Data is encrypted on the client before it ever leaves the device. Under the hood it uses [Automerge](https://automerge.org/) CRDTs so multiple users can edit concurrently and conflicts resolve automatically.
|
|
10
10
|
|
|
11
11
|
**Haven** is the browser-based workspace that sits on top of MindooDB. Users organize databases, applications, notes, and media on a visual grid -- think of it as a customizable home screen for encrypted data that works offline and syncs in the background.
|
|
12
12
|
|
|
@@ -772,11 +772,227 @@ Like the text buffer, the rich-text handle always sends the original `baseHeads`
|
|
|
772
772
|
|
|
773
773
|
#### End-to-end sample
|
|
774
774
|
|
|
775
|
-
The Word/.docx mode in [`mindoodb-app-teamedit`](https://github.com/klehmann/mindoodb-app-teamedit)
|
|
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
|
+
|
|
896
|
+
### Queries & Live Queries
|
|
897
|
+
|
|
898
|
+
`documents.query()` runs ad-hoc queries against the database's **document summary buffer** on the host: declarative filtering, dynamic sorting, paging, and field projection — without materializing documents. This is the fastest way to find and order documents from an app and the right default for lists, search results, and dashboards.
|
|
899
|
+
|
|
900
|
+
```ts
|
|
901
|
+
import { createViewLanguage } from "mindoodb-app-sdk";
|
|
902
|
+
|
|
903
|
+
const v = createViewLanguage<{ type: string; total: number }>();
|
|
904
|
+
|
|
905
|
+
const result = await db.documents.query({
|
|
906
|
+
filter: v.and(
|
|
907
|
+
v.eq(v.field("type"), v.string("invoice")),
|
|
908
|
+
v.gt(v.toNumber(v.field("total")), v.number(100)),
|
|
909
|
+
),
|
|
910
|
+
sortBy: [{ field: "total", direction: "descending" }],
|
|
911
|
+
limit: 50,
|
|
912
|
+
offset: 0,
|
|
913
|
+
fields: ["total", "customer"],
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
console.log(result.total); // matching docs before paging
|
|
917
|
+
console.log(result.coverage); // "full" | "rebuilding" | "full-scan"
|
|
918
|
+
for (const row of result.rows) {
|
|
919
|
+
console.log(row.docId, row.fields, row.lastModified);
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
The `filter` also accepts formula source text, which the SDK parses locally before it crosses the bridge (only the JSON expression AST travels):
|
|
924
|
+
|
|
925
|
+
```ts
|
|
926
|
+
const invoices = await db.documents.query({
|
|
927
|
+
filter: 'v.eq(v.field("type"), "invoice")',
|
|
928
|
+
});
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
Notes:
|
|
932
|
+
|
|
933
|
+
- The host caps `limit` (default 200, maximum 1000) so result payloads stay bounded. Page with `offset` for more rows.
|
|
934
|
+
- `coverage: "rebuilding"` means the summary buffer is still backfilling — results may be incomplete and will improve on their own.
|
|
935
|
+
- Queries that reference encrypted fields or fields outside the summary configuration are rejected with a `query-not-supported` bridge error. Use a view with `useFullDocuments` (see below) or `documents.list()` for those cases.
|
|
936
|
+
- Queries require the `read` capability and are not available in time-travel launches (the summary buffer reflects the live state only).
|
|
937
|
+
|
|
938
|
+
**Full-text search** — a query can additionally carry a `text` clause, evaluated against the database's client-side full-text index on the host and combined with `filter` as a logical AND:
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
const result = await db.documents.query({
|
|
942
|
+
text: { query: "solar panels" }, // full-text match + relevance score
|
|
943
|
+
filter: 'v.eq(v.field("type"), "article")', // optional structured filter
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
for (const row of result.rows) {
|
|
947
|
+
console.log(row.docId, row.textScore); // higher score = better match
|
|
948
|
+
}
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
- Options: `fields` (restrict matching to specific index fields), `prefix` (default `true` — `"sol"` matches `"solar"`), `fuzzy` (edit-distance tolerance, default off), `combineWith` (`"AND"` default | `"OR"`).
|
|
952
|
+
- Rows gain a `textScore`; without an explicit `sortBy`, results come best score first. Sorting can reference the score explicitly with `sortBy: [{ special: "textScore", direction: "descending" }]`.
|
|
953
|
+
- Full-text indexing is **opt-in per database** (`fulltextSetup` in the `dbsetup` document). A `text` clause against a database without it is rejected with a `fulltext-not-enabled` bridge error — catch it and hide/disable the search UI.
|
|
954
|
+
- `liveQuery` supports `text` clauses unchanged; the host rounds scores in its result fingerprint so minor relevance drift does not spam your callback.
|
|
955
|
+
|
|
956
|
+
**Enabling full-text indexing** — apps turn indexing on themselves through the database handle. The configuration is stored in the synced `dbsetup` document, so it applies on every replica (each device builds its own local, encrypted index):
|
|
957
|
+
|
|
958
|
+
```ts
|
|
959
|
+
// Idempotent — safe to call on every app start (no write when unchanged).
|
|
960
|
+
// Requires the `update` capability.
|
|
961
|
+
await db.setFulltextSetup({
|
|
962
|
+
enabled: true,
|
|
963
|
+
attachments: true, // index text extracted from attachments (PDF, Office, …)
|
|
964
|
+
language: "de", // BCP-47 tag steering tokenization; omit for language-neutral
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const setup = await db.getFulltextSetup(); // null when never configured
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
- Without `include`, every non-underscore top-level field with extractable text is indexed (auto mode). Pass `include: ["subject", "body"]` to pin the indexed fields, including nested/long-text fields outside the summary buffer.
|
|
971
|
+
- `attachments: true` indexes attachment text under the synthetic `_attachments` field using the host's extractors (Haven ships PDF, Office, and plain-text extractors). Restrict a query to attachment content with `text: { query, fields: ["_attachments"] }`.
|
|
972
|
+
- Changing the configuration (including `language`) rebuilds the index in the background; queries during the rebuild report `coverage: "rebuilding"`.
|
|
973
|
+
|
|
974
|
+
**Live queries** keep a query result up to date. The callback receives the initial result and runs again whenever the result actually changed — the host coalesces bursts of writes and fingerprints results, so you only hear about real changes:
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
const subscription = await db.documents.liveQuery(
|
|
978
|
+
{ filter: 'v.eq(v.field("status"), "open")', limit: 100 },
|
|
979
|
+
(result) => {
|
|
980
|
+
renderList(result.rows);
|
|
981
|
+
},
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
// Force a re-evaluation (delivers a result even when nothing changed):
|
|
985
|
+
await subscription.refresh();
|
|
986
|
+
|
|
987
|
+
// Always dispose when the consuming UI goes away:
|
|
988
|
+
await subscription.dispose();
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
Live query results are pushed from the host over the bridge port — no polling. Subscriptions are cleaned up automatically when the app disconnects, but dispose them explicitly when the UI that consumes them unmounts.
|
|
776
992
|
|
|
777
993
|
### Incremental sync
|
|
778
994
|
|
|
779
|
-
The changefeed-backed `documents.list()` API is also the right primitive for app-side indexes, search, and other derived caches.
|
|
995
|
+
The changefeed-backed `documents.list()` API is also the right primitive for app-side indexes, search, and other derived caches. For plain "show me matching documents" use cases, prefer [`documents.query()`](#queries--live-queries); the changefeed shines when you need deletions and a durable resume checkpoint for derived state.
|
|
780
996
|
|
|
781
997
|
**Initial scan from the beginning:**
|
|
782
998
|
|
|
@@ -1106,13 +1322,28 @@ await navigator.setExpansionState(expansion);
|
|
|
1106
1322
|
await navigator.setSelectionState(selection);
|
|
1107
1323
|
```
|
|
1108
1324
|
|
|
1325
|
+
**Live updates** -- the host keeps the view bound to its source databases' change feeds. Subscribe with `onDidUpdate` and re-read the visible page when it fires; there is no need to poll or call `refresh()`:
|
|
1326
|
+
|
|
1327
|
+
```ts
|
|
1328
|
+
const unsubscribe = navigator.onDidUpdate(({ addedCount, removedCount }) => {
|
|
1329
|
+
console.log(`view changed: +${addedCount} / -${removedCount}`);
|
|
1330
|
+
void rerenderVisiblePage();
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// later, when the consuming UI unmounts:
|
|
1334
|
+
unsubscribe();
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1109
1337
|
The expression language is fully declarative: apps define filter and column logic through a typed builder API that compiles to JSON-safe expression objects. No app-provided JavaScript runs inside the bridge host.
|
|
1110
1338
|
|
|
1111
1339
|
#### Performance notes
|
|
1112
1340
|
|
|
1341
|
+
- Navigator views are built **summary-first**: whenever the view definition (filter + column expressions) can be answered from the database's document summary buffer, the host feeds the view from the summary without materializing a single document. Views fall back to the (slower) document path automatically when they need decrypted fields, JS value functions, or fields outside the summary configuration.
|
|
1342
|
+
- Pass `useFullDocuments: true` in `createViewNavigator` input to force the document path explicitly — the escape hatch for views that deliberately need full document data.
|
|
1113
1343
|
- `goto*()` and `getCurrentEntry()` are lightweight stateful cursor operations. They may still produce many bridge round-trips if you call them for every rendered row.
|
|
1114
1344
|
- `entriesForward()` and `entriesBackward()` exist specifically to reduce those round-trips for UI rendering.
|
|
1115
|
-
- `refresh()`
|
|
1345
|
+
- `refresh()` performs an incremental catch-up on the host-side view (no pipeline rebuild). With `onDidUpdate` in place you rarely need it — the host pushes updates on its own.
|
|
1346
|
+
- `session.listDocumentsSinceViewCursor(cursor)` remains available as a **legacy polling/delta-sync path**; prefer `onDidUpdate` pushes and `documents.liveQuery()` for keeping UI current.
|
|
1116
1347
|
- Child/key/range helpers operate on the same host-side navigator session. They do not create a separate query engine.
|
|
1117
1348
|
|
|
1118
1349
|
Available expression helpers:
|
|
@@ -1124,7 +1355,7 @@ Available expression helpers:
|
|
|
1124
1355
|
- **String helpers:** `concat()`, `lower()`, `upper()`, `trim()`, `contains()`, `startsWith()`, `endsWith()`
|
|
1125
1356
|
- **Null/existence:** `coalesce()`, `exists()`, `notExists()`
|
|
1126
1357
|
- **Date/path:** `datePart()`, `pathJoin()`
|
|
1127
|
-
- **Control flow:** `ifElse()`, `let()`
|
|
1358
|
+
- **Control flow:** `ifElse()`, `let()` — `ifElse()` accepts multiple condition/value pairs followed by a default, like Domino's `@If`: `v.ifElse(cond1, val1, cond2, val2, default)`
|
|
1128
1359
|
|
|
1129
1360
|
The full language guide lives in the `mindoodb-view-language` package. The SDK re-exports `createViewLanguage()` for convenience.
|
|
1130
1361
|
|
|
@@ -1195,19 +1426,25 @@ Connect options: `launchId?`, `targetOrigin?`, `connectTimeoutMs?`.
|
|
|
1195
1426
|
|
|
1196
1427
|
### MindooDBAppDatabase
|
|
1197
1428
|
|
|
1198
|
-
| Property / Method
|
|
1199
|
-
|
|
|
1200
|
-
| `info()`
|
|
1201
|
-
| `documents`
|
|
1202
|
-
| `attachments`
|
|
1429
|
+
| Property / Method | Returns |
|
|
1430
|
+
| ---------------------------- | ---------------------------------------------- |
|
|
1431
|
+
| `info()` | `Promise<MindooDBAppDatabaseInfo>` |
|
|
1432
|
+
| `documents` | `MindooDBAppDocumentApi` |
|
|
1433
|
+
| `attachments` | `MindooDBAppAttachmentApi` |
|
|
1434
|
+
| `getFulltextSetup()` | `Promise<MindooDBAppFulltextSetup \| null>` |
|
|
1435
|
+
| `setFulltextSetup(config)` | `Promise<void>` |
|
|
1203
1436
|
|
|
1204
1437
|
### MindooDBAppDocumentApi
|
|
1205
1438
|
|
|
1206
1439
|
| Method | Returns |
|
|
1207
1440
|
| ---------------------------------- | -------------------------------------------- |
|
|
1441
|
+
| `query(query?)` | `Promise<MindooDBAppQueryResult>` |
|
|
1442
|
+
| `liveQuery(query, onResult)` | `Promise<MindooDBAppLiveQuerySubscription>` |
|
|
1208
1443
|
| `list(query?)` | `Promise<MindooDBAppDocumentListResult>` |
|
|
1209
1444
|
| `get(docId)` | `Promise<MindooDBAppDocument \| null>` |
|
|
1210
1445
|
| `getRichText(docId, path, opts?)` | `Promise<MindooDBAppRichTextSnapshot>` |
|
|
1446
|
+
| `getAutomergeSnapshot(docId, opts?)` | `Promise<MindooDBAppAutomergeSnapshot>` |
|
|
1447
|
+
| `applyAutomergeChanges(docId, patch)` | `Promise<MindooDBAppAutomergePatchResult>` |
|
|
1211
1448
|
| `create(input)` | `Promise<MindooDBAppDocument>` |
|
|
1212
1449
|
| `update(docId, patch)` | `Promise<MindooDBAppDocument>` |
|
|
1213
1450
|
| `delete(docId)` | `Promise<{ ok: true }>` |
|
|
@@ -1216,6 +1453,8 @@ Connect options: `launchId?`, `targetOrigin?`, `connectTimeoutMs?`.
|
|
|
1216
1453
|
| `getAtTimestamp(docId, timestamp)` | `Promise<MindooDBAppHistoricalDocument>` |
|
|
1217
1454
|
| `getAtRevision(docId, revisionId)` | `Promise<MindooDBAppHistoricalDocument>` |
|
|
1218
1455
|
|
|
1456
|
+
`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).
|
|
1457
|
+
|
|
1219
1458
|
`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.
|
|
1220
1459
|
|
|
1221
1460
|
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.
|
|
@@ -1255,7 +1494,9 @@ interface MindooDBAppUpdateDocumentInput {
|
|
|
1255
1494
|
| Method | Returns |
|
|
1256
1495
|
| ---------------------------------------------------------- | ------------------------------------------------- |
|
|
1257
1496
|
| `getDefinition()` | `Promise<MindooDBAppViewDefinition>` |
|
|
1258
|
-
| `refresh()` | `Promise<
|
|
1497
|
+
| `refresh()` | `Promise<string \| null>` (new view cursor) |
|
|
1498
|
+
| `onDidUpdate(listener)` | `() => void` (unsubscribe) |
|
|
1499
|
+
| `getViewCursor()` | `Promise<string \| null>` |
|
|
1259
1500
|
| `getCurrentEntry()` | `Promise<MindooDBAppViewEntry \| null>` |
|
|
1260
1501
|
| `gotoFirst()` / `gotoLast()` | `Promise<boolean>` |
|
|
1261
1502
|
| `gotoNext()` / `gotoPrev()` | `Promise<boolean>` |
|
package/TESTING.md
CHANGED
|
@@ -12,6 +12,15 @@ import {
|
|
|
12
12
|
} from "mindoodb-app-sdk/testing";
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
The testing entrypoint emulates document storage with real Automerge documents,
|
|
16
|
+
so `@automerge/automerge` is declared as an optional peer dependency. Add it to
|
|
17
|
+
your app's `devDependencies` when you import `mindoodb-app-sdk/testing`
|
|
18
|
+
(the runtime SDK entrypoint does not need it):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add -D @automerge/automerge
|
|
22
|
+
```
|
|
23
|
+
|
|
15
24
|
## Pick a level
|
|
16
25
|
|
|
17
26
|
### Level 1: simple app tests
|
|
@@ -93,6 +102,30 @@ Each database entry can provide:
|
|
|
93
102
|
- `methods.documents`
|
|
94
103
|
- `methods.views` for session-level `createView()` and `openView()` calls
|
|
95
104
|
- `methods.attachments`
|
|
105
|
+
- `fulltextSetup` — the initial config returned by `db.getFulltextSetup()`; `db.setFulltextSetup()` overwrites it for the lifetime of the handle. Use this to test your app's full-text bootstrap logic. Note the mock evaluates `text` query clauses regardless of this config.
|
|
106
|
+
|
|
107
|
+
The default in-memory document store also implements `documents.query()` and `documents.liveQuery()`: expression and formula-string filters are evaluated against the seeded documents, `sortBy`/`limit`/`offset` work as documented, and live query callbacks fire automatically after `create`/`update`/`delete`/`undelete` mutations. Full-text `text` clauses are supported with a deterministic mock implementation (token matching with prefix/AND semantics and an occurrence-count `textScore`) — assert on membership and relative ordering, not absolute scores, since real hosts use a BM25-style engine. That means app code built on queries and live queries is testable at Level 1 without any extra setup:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const session = await mock.bridge.connect();
|
|
111
|
+
const db = await session.openDatabase("main");
|
|
112
|
+
|
|
113
|
+
await db.documents.create({ set: { type: "invoice", total: 120 } });
|
|
114
|
+
|
|
115
|
+
const result = await db.documents.query({
|
|
116
|
+
filter: 'v.eq(v.field("type"), "invoice")',
|
|
117
|
+
sortBy: [{ field: "total", direction: "descending" }],
|
|
118
|
+
});
|
|
119
|
+
expect(result.rows).toHaveLength(1);
|
|
120
|
+
|
|
121
|
+
const updates: number[] = [];
|
|
122
|
+
const sub = await db.documents.liveQuery(
|
|
123
|
+
{ filter: 'v.eq(v.field("type"), "invoice")' },
|
|
124
|
+
(r) => updates.push(r.total),
|
|
125
|
+
);
|
|
126
|
+
await db.documents.create({ set: { type: "invoice", total: 50 } });
|
|
127
|
+
await sub.dispose();
|
|
128
|
+
```
|
|
96
129
|
|
|
97
130
|
## Level 2 example
|
|
98
131
|
|
|
@@ -161,10 +194,14 @@ Useful Level 2 methods:
|
|
|
161
194
|
- `dispose()`
|
|
162
195
|
- `emitThemeChange()`
|
|
163
196
|
- `emitViewportChange()`
|
|
197
|
+
- `emitQueryResult()` — push a `query-result` message to live query subscribers
|
|
198
|
+
- `emitViewChanged()` — push a `view-changed` message to navigator `onDidUpdate` listeners
|
|
164
199
|
- `setRequestHandler()`
|
|
165
200
|
- `clearRequestHandler()`
|
|
166
201
|
- `postPortMessage()`
|
|
167
202
|
|
|
203
|
+
The built-in request handling also covers `documents.query` and the `documents.liveQuery.*` RPCs against the seeded documents, so `db.documents.query()` / `db.documents.liveQuery()` and `navigator.onDidUpdate()` work end-to-end over the real bridge transport in Level 2 tests.
|
|
204
|
+
|
|
168
205
|
## When to use which level
|
|
169
206
|
|
|
170
207
|
Choose Level 1 when:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppBridge, b as MindooDBAppDatabase, c as MindooDBAppDocument, d as MindooDBAppRichTextSpan, e as MindooDBAppRichTextSnapshot } from './types-
|
|
2
|
-
export { f as
|
|
1
|
+
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppBridge, b as MindooDBAppDatabase, c as MindooDBAppDocument, d as MindooDBAppRichTextSpan, e as MindooDBAppRichTextSnapshot } from './types-B6vEy1VC.js';
|
|
2
|
+
export { f as MindooDBAppAccessDecision, g as MindooDBAppAccessTier, h as MindooDBAppAttachmentApi, i as MindooDBAppAttachmentChunk, j as MindooDBAppAttachmentInfo, k as MindooDBAppAttachmentPreviewOptions, l as MindooDBAppAttachmentPreviewSession, m as MindooDBAppAutomergeChangesPatch, n as MindooDBAppAutomergeChangesSince, o as MindooDBAppAutomergeGetOptions, p as MindooDBAppAutomergePatchResult, q as MindooDBAppAutomergeSnapshot, r as MindooDBAppBridgeConnectMessage, s as MindooDBAppBridgeConnectOptions, t as MindooDBAppBridgeConnectedMessage, u as MindooDBAppBridgeErrorPayload, v as MindooDBAppBridgeHandshakeErrorMessage, w as MindooDBAppBridgeLocaleChangedMessage, x as MindooDBAppBridgePortMessage, y as MindooDBAppBridgeQueryResultMessage, z as MindooDBAppBridgeRpcError, A as MindooDBAppBridgeRpcMessage, B as MindooDBAppBridgeRpcRequest, C as MindooDBAppBridgeRpcSuccess, D as MindooDBAppBridgeStreamAbortRequest, E as MindooDBAppBridgeStreamAck, F as MindooDBAppBridgeStreamCloseRequest, G as MindooDBAppBridgeStreamError, H as MindooDBAppBridgeStreamMessage, I as MindooDBAppBridgeStreamOpenResult, J as MindooDBAppBridgeStreamReadRequest, K as MindooDBAppBridgeStreamWriteRequest, L as MindooDBAppBridgeThemeChangedMessage, N as MindooDBAppBridgeUiPreferencesChangedMessage, O as MindooDBAppBridgeViewChangedMessage, P as MindooDBAppBridgeViewportChangedMessage, Q as MindooDBAppCapability, R as MindooDBAppConfiguredViewCategorizationStyle, S as MindooDBAppConfiguredViewColumn, T as MindooDBAppConfiguredViewColumnRole, U as MindooDBAppConfiguredViewExpressionMode, V as MindooDBAppConfiguredViewFieldExpression, W as MindooDBAppConfiguredViewFilterDefinition, X as MindooDBAppConfiguredViewFilterMode, Y as MindooDBAppConfiguredViewFilterRule, Z as MindooDBAppConfiguredViewFormulaExpression, _ as MindooDBAppConfiguredViewFormulaFilterDefinition, $ as MindooDBAppConfiguredViewPreviewMode, a0 as MindooDBAppConfiguredViewRuleFilterDefinition, a1 as MindooDBAppConfiguredViewRuleMatchMode, a2 as MindooDBAppConfiguredViewRuleOperator, a3 as MindooDBAppConfiguredViewValueExpression, a4 as MindooDBAppCreateDocumentInput, a5 as MindooDBAppCreateViewInput, a6 as MindooDBAppCreateViewNavigatorInput, a7 as MindooDBAppDatabaseInfo, a8 as MindooDBAppDocumentApi, a9 as MindooDBAppDocumentHeadCursorResult, aa as MindooDBAppDocumentHistoryEntry, ab as MindooDBAppDocumentListQuery, ac as MindooDBAppDocumentListResult, ad as MindooDBAppDocumentQuery, ae as MindooDBAppDocumentRevisionId, af as MindooDBAppDocumentScanPreset, ag as MindooDBAppDocumentSummary, ah as MindooDBAppExtractionSetup, ai as MindooDBAppFulltextSetup, aj as MindooDBAppHistoricalDocument, ak as MindooDBAppHistoryQuery, al as MindooDBAppHostTheme, am as MindooDBAppJsonListDeletePatch, an as MindooDBAppJsonListInsertPatch, ao as MindooDBAppJsonPatch, ap as MindooDBAppJsonSetPatch, aq as MindooDBAppJsonTextMarkPatch, ar as MindooDBAppJsonTextSplicePatch, as as MindooDBAppJsonTextUnmarkPatch, at as MindooDBAppJsonUnsetPatch, au as MindooDBAppLaunchContext, av as MindooDBAppLiveQuerySubscription, aw as MindooDBAppMenuAnchor, ax as MindooDBAppMenuApi, ay as MindooDBAppMenuCommandItem, az as MindooDBAppMenuDismissReason, aA as MindooDBAppMenuDismissedResult, aB as MindooDBAppMenuItem, aC as MindooDBAppMenuKind, aD as MindooDBAppMenuPlacement, aE as MindooDBAppMenuPointAnchor, aF as MindooDBAppMenuRect, aG as MindooDBAppMenuRectAnchor, aH as MindooDBAppMenuSelectionResult, aI as MindooDBAppMenuSeparatorItem, aJ as MindooDBAppQueryCoverage, aK as MindooDBAppQueryResult, aL as MindooDBAppQueryRow, aM as MindooDBAppQuerySortKey, aN as MindooDBAppQueryTextClause, aO as MindooDBAppReadableAttachmentStream, aP as MindooDBAppResolvedViewDefinition, aQ as MindooDBAppResolvedViewSource, aR as MindooDBAppRichTextBlockSpan, aS as MindooDBAppRichTextGetOptions, aT as MindooDBAppRichTextImmutableString, aU as MindooDBAppRichTextMarkRange, aV as MindooDBAppRichTextMaterializeValue, aW as MindooDBAppRichTextPatch, aX as MindooDBAppRichTextScalar, aY as MindooDBAppRichTextSpliceStep, aZ as MindooDBAppRichTextStep, a_ as MindooDBAppRichTextStepPatch, a$ as MindooDBAppRichTextTextSpan, b0 as MindooDBAppRuntime, b1 as MindooDBAppScanAttachmentOptions, b2 as MindooDBAppScanAttachmentResult, b3 as MindooDBAppScopedDocId, b4 as MindooDBAppScopedDocumentSummary, b5 as MindooDBAppSession, b6 as MindooDBAppShowMenuInput, b7 as MindooDBAppShowMenuResult, b8 as MindooDBAppTextEdit, b9 as MindooDBAppTextPatch, ba as MindooDBAppThemeMode, bb as MindooDBAppUiPreferences, bc as MindooDBAppUpdateDocumentInput, bd as MindooDBAppViewCursorDocumentListResult, be as MindooDBAppViewCursorResult, bf as MindooDBAppViewEntry, bg as MindooDBAppViewEntryKind, bh as MindooDBAppViewNavigator, bi as MindooDBAppViewNavigatorExpansionState, bj as MindooDBAppViewNavigatorOpenOptions, bk as MindooDBAppViewNavigatorPageOptions, bl as MindooDBAppViewNavigatorPageResult, bm as MindooDBAppViewNavigatorRangeQuery, bn as MindooDBAppViewNavigatorSelectionState, bo as MindooDBAppViewUpdateStats, bp as MindooDBAppViewport, bq as MindooDBAppWritableAttachmentStream } from './types-B6vEy1VC.js';
|
|
3
|
+
import { MindooDBAppBooleanExpression } from 'mindoodb-view-language';
|
|
3
4
|
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';
|
|
5
|
+
import { MindooDB, MindooQuerySortKey, MindooQueryOptions, MindooQueryResult } from 'mindoodb';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Resolves whether Haven can render a built-in preview for an attachment.
|
|
@@ -53,13 +55,15 @@ type MindooDBRichTextHandleChangeListener = (snapshot: MindooDBAppRichTextSnapsh
|
|
|
53
55
|
/**
|
|
54
56
|
* Local rich-text span buffer for SDK apps.
|
|
55
57
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
58
|
+
* Mirrors `MindooDBTextBuffer`: track edits against a base snapshot, then flush
|
|
59
|
+
* with causal `baseHeads`. Text edits prefer positional `richTextSteps` so
|
|
60
|
+
* concurrent saves merge like plain-text splices; formatting-only or structural
|
|
61
|
+
* changes fall back to full `richText` span snapshots.
|
|
59
62
|
*/
|
|
60
63
|
declare class MindooDBRichTextHandle {
|
|
61
64
|
private readonly options;
|
|
62
65
|
private spansInternal;
|
|
66
|
+
private baseMaterializedText;
|
|
63
67
|
private heads;
|
|
64
68
|
private dirtyInternal;
|
|
65
69
|
private listeners;
|
|
@@ -77,6 +81,7 @@ declare class MindooDBRichTextHandle {
|
|
|
77
81
|
refresh(): Promise<MindooDBAppRichTextSnapshot>;
|
|
78
82
|
pollOnce(): Promise<boolean>;
|
|
79
83
|
flush(updateSpansConfig?: Record<string, unknown>): Promise<MindooDBRichTextHandleFlushResult>;
|
|
84
|
+
private flushTextSteps;
|
|
80
85
|
private emitChange;
|
|
81
86
|
}
|
|
82
87
|
declare function createMindooDBRichTextHandle(options: CreateMindooDBRichTextHandleOptions): MindooDBRichTextHandle;
|
|
@@ -174,4 +179,11 @@ declare class MindooDBTextBuffer {
|
|
|
174
179
|
/** Create a local text buffer for one string field in a document. */
|
|
175
180
|
declare function createMindooDBTextBuffer(options: CreateMindooDBTextBufferOptions): MindooDBTextBuffer;
|
|
176
181
|
|
|
177
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Thin convenience wrapper around `db.query()`: accepts the filter either
|
|
184
|
+
* as a prebuilt expression (from `createViewLanguage()`) or as formula
|
|
185
|
+
* source text, which is parsed with the shared formula parser.
|
|
186
|
+
*/
|
|
187
|
+
declare function queryDocuments(db: Pick<MindooDB, "query">, filter: MindooDBAppBooleanExpression | string, sortBy?: MindooQuerySortKey[], options?: MindooQueryOptions): Promise<MindooQueryResult>;
|
|
188
|
+
|
|
189
|
+
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, queryDocuments };
|