mindoodb-app-sdk 0.0.49 → 0.0.53

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
+ MINDOODB_APP_VALUE_TAG,
2
3
  releaseMindooDBAppBridgeSessions
3
- } from "../chunk-TPYDERAK.js";
4
+ } from "../chunk-6NTVTL6G.js";
4
5
 
5
6
  // src/testing/index.ts
6
7
  import * as Automerge from "@automerge/automerge";
@@ -28,10 +29,10 @@ function toFormulaBoolean(value) {
28
29
  }
29
30
  return Boolean(value);
30
31
  }
31
- function matchesFilter(mindoodb, definition, document) {
32
+ function matchesFilter(mindoodb, definition, document2) {
32
33
  void mindoodb;
33
34
  if (!definition.filter) return true;
34
- const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document;
35
+ const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document2;
35
36
  return toFormulaBoolean(
36
37
  evaluateExpression(definition.filter.expression, {
37
38
  doc,
@@ -46,8 +47,8 @@ function matchesFilter(mindoodb, definition, document) {
46
47
  })
47
48
  );
48
49
  }
49
- function computeColumnValues(definition, document) {
50
- const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document;
50
+ function computeColumnValues(definition, document2) {
51
+ const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document2;
51
52
  const values = {};
52
53
  for (const [key, value] of Object.entries(doc)) {
53
54
  if (!key.startsWith("_")) values[key] = value;
@@ -171,13 +172,13 @@ async function buildVirtualView(mindoodb, definition, documents, categorizationS
171
172
  }
172
173
  const view = await builder.build();
173
174
  const changes = /* @__PURE__ */ new Map();
174
- for (const document of documents) {
175
- if (!matchesFilter(mindoodb, definition, document)) {
175
+ for (const document2 of documents) {
176
+ if (!matchesFilter(mindoodb, definition, document2)) {
176
177
  continue;
177
178
  }
178
- const change = changes.get(document.origin) ?? new mindoodb.VirtualViewDataChange(document.origin);
179
- change.addEntry(document.docId, computeColumnValues(definition, document));
180
- changes.set(document.origin, change);
179
+ const change = changes.get(document2.origin) ?? new mindoodb.VirtualViewDataChange(document2.origin);
180
+ change.addEntry(document2.docId, computeColumnValues(definition, document2));
181
+ changes.set(document2.origin, change);
181
182
  }
182
183
  for (const change of changes.values()) {
183
184
  if (change.hasChanges()) view.applyChanges(change);
@@ -453,6 +454,249 @@ async function createEvaluatingViewNavigator(input, documents) {
453
454
  return wrapNavigator(input.definition, navigator);
454
455
  }
455
456
 
457
+ // src/testing/browserTestHost.ts
458
+ function capabilitiesForDefinitionPermissions(permissions) {
459
+ const result = /* @__PURE__ */ new Set(["read"]);
460
+ for (const permission of permissions ?? []) {
461
+ if (permission === "write") {
462
+ result.add("create");
463
+ result.add("update");
464
+ } else if (permission === "delete" || permission === "history" || permission === "attachments" || permission === "views" || permission === "sign" || permission === "timestamps" || permission === "directory" || permission === "sealedchannel") {
465
+ result.add(permission);
466
+ }
467
+ }
468
+ return [...result];
469
+ }
470
+ function mockDatabasesFromDefinition(definition, seed = {}) {
471
+ return (definition.databases ?? []).map((database) => ({
472
+ info: {
473
+ id: database.logicalDatabaseId,
474
+ title: database.label ?? database.logicalDatabaseId,
475
+ capabilities: capabilitiesForDefinitionPermissions(database.permissions)
476
+ },
477
+ documents: seed[database.logicalDatabaseId]
478
+ }));
479
+ }
480
+ function withLaunchId(appUrl, launchId) {
481
+ const url = new URL(appUrl, window.location.href);
482
+ url.searchParams.set("mindoodbAppLaunchId", launchId);
483
+ return url.toString();
484
+ }
485
+ function createBrowserTestHost(options) {
486
+ const { frame, appUrl, onChange, ...hostOptions } = options;
487
+ let nextScan = null;
488
+ let scanCounter = 0;
489
+ const log = {
490
+ notifications: [],
491
+ previews: [],
492
+ scans: [],
493
+ focusRequests: 0,
494
+ requests: []
495
+ };
496
+ const host = createFakeBridgeHost({
497
+ ...hostOptions,
498
+ onRequest(request) {
499
+ log.requests.push(request);
500
+ const params = request.params ?? {};
501
+ if (request.method === "notifications.show") {
502
+ log.notifications.push({
503
+ id: typeof params.id === "string" ? params.id : void 0,
504
+ severity: typeof params.severity === "string" ? params.severity : void 0,
505
+ text: typeof params.text === "string" ? params.text : void 0,
506
+ durationMs: typeof params.durationMs === "number" ? params.durationMs : void 0
507
+ });
508
+ } else if (request.method === "session.requestHostFocus") {
509
+ log.focusRequests += 1;
510
+ } else if (request.method === "attachments.openPreview" || request.method === "attachments.preparePreviewSession") {
511
+ log.previews.push({
512
+ databaseId: String(params.databaseId),
513
+ docId: String(params.docId),
514
+ attachmentName: String(params.attachmentName)
515
+ });
516
+ }
517
+ hostOptions.onRequest?.(request);
518
+ queueMicrotask(() => onChange?.());
519
+ },
520
+ requestHandlers: {
521
+ "attachments.scanAndWrite": (params) => {
522
+ const input = params ?? {};
523
+ const scan = nextScan;
524
+ nextScan = null;
525
+ log.scans.push({
526
+ databaseId: String(input.databaseId),
527
+ docId: String(input.docId),
528
+ result: scan ? "scanned" : "cancelled"
529
+ });
530
+ if (!scan) {
531
+ return { ok: false };
532
+ }
533
+ scanCounter += 1;
534
+ return {
535
+ ok: true,
536
+ attachment: {
537
+ attachmentId: `test-scan-${scanCounter}`,
538
+ fileName: scan.fileName,
539
+ mimeType: scan.mimeType,
540
+ size: scan.size
541
+ }
542
+ };
543
+ },
544
+ ...hostOptions.requestHandlers
545
+ }
546
+ });
547
+ const onMessage = (event) => {
548
+ if (event.source !== frame.contentWindow) {
549
+ return;
550
+ }
551
+ host.acceptConnection(event.data, event.ports);
552
+ };
553
+ window.addEventListener("message", onMessage);
554
+ frame.src = withLaunchId(appUrl, host.launchId);
555
+ return {
556
+ host,
557
+ log,
558
+ setNextScan(scan) {
559
+ nextScan = scan;
560
+ },
561
+ setTheme(theme) {
562
+ host.emitThemeChange(theme);
563
+ onChange?.();
564
+ },
565
+ setHostFocus(focused) {
566
+ host.emitHostFocusChange(focused);
567
+ onChange?.();
568
+ },
569
+ reloadApp() {
570
+ frame.src = withLaunchId(appUrl, host.launchId);
571
+ },
572
+ dispose() {
573
+ window.removeEventListener("message", onMessage);
574
+ host.dispose();
575
+ }
576
+ };
577
+ }
578
+ var STYLE = `
579
+ .htest{display:grid;grid-template-columns:minmax(0,1fr) 300px;height:100vh;margin:0;font:13px/1.45 system-ui,sans-serif;background:#eef1f7;color:#172033}
580
+ .htest *{box-sizing:border-box}
581
+ .htest__frame{width:100%;height:100%;border:0;background:#fff}
582
+ .htest__panel{border-left:1px solid #d7dce8;overflow:auto;padding:14px;background:#f8f9fc}
583
+ .htest__badge{display:inline-block;padding:2px 8px;border-radius:999px;background:#fde68a;color:#713f12;font-weight:600;font-size:11px;letter-spacing:.04em}
584
+ .htest h1{font-size:15px;margin:8px 0 12px}
585
+ .htest h2{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:#5b6478;margin:16px 0 6px}
586
+ .htest__row{display:flex;gap:6px;flex-wrap:wrap}
587
+ .htest button{font:inherit;padding:5px 9px;border-radius:7px;border:1px solid #c9d0e0;background:#fff;cursor:pointer}
588
+ .htest ul{list-style:none;margin:0;padding:0}
589
+ .htest li{padding:5px 0;border-bottom:1px solid #e4e8f1;word-break:break-word}
590
+ .htest__muted{color:#5b6478}
591
+ @media (max-width:720px){.htest{grid-template-columns:1fr;grid-template-rows:65vh auto}.htest__panel{border-left:0;border-top:1px solid #d7dce8}}
592
+ `;
593
+ function item(text) {
594
+ const li = document.createElement("li");
595
+ li.textContent = text;
596
+ return li;
597
+ }
598
+ function mountHavenTestHost(options) {
599
+ const { container = document.body, title = "Haven test host", ...hostOptions } = options;
600
+ const style = document.createElement("style");
601
+ style.textContent = STYLE;
602
+ document.head.append(style);
603
+ const root = document.createElement("div");
604
+ root.className = "htest";
605
+ const frame = document.createElement("iframe");
606
+ frame.className = "htest__frame";
607
+ frame.title = title;
608
+ frame.dataset.testid = "haven-test-app-frame";
609
+ const panel = document.createElement("aside");
610
+ panel.className = "htest__panel";
611
+ root.append(frame, panel);
612
+ container.replaceChildren(root);
613
+ if (container === document.body) {
614
+ document.body.style.margin = "0";
615
+ }
616
+ let focused = false;
617
+ let dark = false;
618
+ const lists = {
619
+ notifications: document.createElement("ul"),
620
+ previews: document.createElement("ul"),
621
+ scans: document.createElement("ul"),
622
+ requests: document.createElement("ul")
623
+ };
624
+ const focusState = document.createElement("span");
625
+ focusState.className = "htest__muted";
626
+ function button(label, onClick) {
627
+ const node = document.createElement("button");
628
+ node.type = "button";
629
+ node.textContent = label;
630
+ node.addEventListener("click", onClick);
631
+ return node;
632
+ }
633
+ function section(label, ...children) {
634
+ const heading2 = document.createElement("h2");
635
+ heading2.textContent = label;
636
+ panel.append(heading2, ...children);
637
+ }
638
+ function render() {
639
+ const { log } = testHost;
640
+ focusState.textContent = `focused: ${focused ? "yes" : "no"} \xB7 requests: ${log.focusRequests}`;
641
+ lists.notifications.replaceChildren(
642
+ ...log.notifications.map((entry) => item(`[${entry.severity ?? "?"}] ${entry.text ?? ""}`))
643
+ );
644
+ lists.previews.replaceChildren(
645
+ ...log.previews.map((entry) => item(`${entry.attachmentName} (${entry.databaseId}/${entry.docId})`))
646
+ );
647
+ lists.scans.replaceChildren(...log.scans.map((entry) => item(`${entry.result} \u2192 ${entry.docId}`)));
648
+ lists.requests.replaceChildren(...log.requests.slice(-30).reverse().map((entry) => item(entry.method)));
649
+ }
650
+ const testHost = createBrowserTestHost({ ...hostOptions, frame, onChange: render });
651
+ const badge = document.createElement("span");
652
+ badge.className = "htest__badge";
653
+ badge.textContent = "TEST HOST \xB7 MOCK DATA";
654
+ const heading = document.createElement("h1");
655
+ heading.textContent = title;
656
+ panel.append(badge, heading);
657
+ const hostRow = document.createElement("div");
658
+ hostRow.className = "htest__row";
659
+ hostRow.append(
660
+ button("Toggle theme", () => {
661
+ dark = !dark;
662
+ testHost.setTheme({ mode: dark ? "dark" : "light", preset: "mindoo" });
663
+ }),
664
+ button("Toggle focus", () => {
665
+ focused = !focused;
666
+ testHost.setHostFocus(focused);
667
+ }),
668
+ button("Reload app", () => testHost.reloadApp())
669
+ );
670
+ section("Host", hostRow, focusState);
671
+ const scanInput = document.createElement("input");
672
+ scanInput.type = "file";
673
+ scanInput.accept = "image/jpeg,image/png,application/pdf";
674
+ scanInput.addEventListener("change", () => {
675
+ const file = scanInput.files?.[0];
676
+ testHost.setNextScan(
677
+ file ? {
678
+ fileName: file.name,
679
+ mimeType: file.type === "image/png" || file.type === "application/pdf" ? file.type : "image/jpeg",
680
+ size: file.size
681
+ } : null
682
+ );
683
+ });
684
+ const scanHint = document.createElement("p");
685
+ scanHint.className = "htest__muted";
686
+ scanHint.textContent = "The app's next scan returns this file. Without one it is cancelled.";
687
+ section("Next scan", scanInput, scanHint);
688
+ section("Notifications", lists.notifications);
689
+ section("Previews", lists.previews);
690
+ section("Scans", lists.scans);
691
+ section("Requests (latest first)", lists.requests);
692
+ void testHost.host.session.getLaunchContext().then((context) => {
693
+ dark = context.theme.mode === "dark";
694
+ });
695
+ render();
696
+ window.__havenTestHost = testHost;
697
+ return testHost;
698
+ }
699
+
456
700
  // src/testing/index.ts
457
701
  var PROTOCOL = "mindoodb-app-bridge";
458
702
  var MOCK_SIGNING_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAmockmockmockmockmockmockmockmockmockmockmoc=\n-----END PUBLIC KEY-----\n";
@@ -473,7 +717,7 @@ function cloneBytes(chunk) {
473
717
  function applyDocumentUpdatePatch(current, patch) {
474
718
  const next = {
475
719
  ...structuredClone(current),
476
- ...patch.set ? structuredClone(patch.set) : {}
720
+ ...patch.set ? readMockTypedValues(patch.set) : {}
477
721
  };
478
722
  for (const key of patch.unset ?? []) {
479
723
  Reflect.deleteProperty(next, key);
@@ -496,7 +740,7 @@ function applyMockJsonPatch(target, patch) {
496
740
  return;
497
741
  }
498
742
  for (const operation of patch.set ?? []) {
499
- setValueAtPath(target, operation.path, structuredClone(operation.value));
743
+ setValueAtPath(target, operation.path, readMockTypedValues(operation.value));
500
744
  }
501
745
  for (const operation of patch.unset ?? []) {
502
746
  unsetValueAtPath(target, operation.path);
@@ -506,8 +750,13 @@ function applyMockJsonPatch(target, patch) {
506
750
  list.splice(operation.index, operation.deleteCount);
507
751
  }
508
752
  for (const operation of patch.listInsert ?? []) {
753
+ const parent = ensureParentAtPath(target, operation.path);
754
+ const leaf = operation.path[operation.path.length - 1];
755
+ if (parent[leaf] == null) {
756
+ parent[leaf] = [];
757
+ }
509
758
  const list = readListAtPath(target, operation.path);
510
- list.splice(operation.index, 0, ...structuredClone(operation.values));
759
+ list.splice(operation.index, 0, ...readMockTypedValues(operation.values));
511
760
  }
512
761
  for (const operation of patch.textSplice ?? []) {
513
762
  applyMockTextPatch(target, operation.path, [{
@@ -522,6 +771,81 @@ function applyMockJsonPatch(target, patch) {
522
771
  for (const operation of patch.textUnmark ?? []) {
523
772
  applyMockTextMarks(target, operation.path, operation.index, operation.length, Object.fromEntries(operation.names.map((name) => [name, null])), true);
524
773
  }
774
+ for (const operation of patch.counterIncrement ?? []) {
775
+ assertMockCounterAmount(operation.delta, "JSON counterIncrement delta");
776
+ const parent = ensureParentAtPath(target, operation.path);
777
+ const leaf = operation.path[operation.path.length - 1];
778
+ const current = parent[leaf];
779
+ if (current == null) {
780
+ parent[leaf] = operation.delta;
781
+ } else if (typeof current === "number") {
782
+ parent[leaf] = current + operation.delta;
783
+ } else {
784
+ throw new Error(
785
+ `Cannot increment non-counter value at ${operation.path.map(String).join(".")}`
786
+ );
787
+ }
788
+ }
789
+ }
790
+ var MOCK_CURSOR_PREFIX = "mock-cursor:";
791
+ function readMockCursorText(data, path) {
792
+ let value = data;
793
+ for (const segment of path) {
794
+ value = value?.[segment];
795
+ }
796
+ if (typeof value !== "string") {
797
+ throw new Error(
798
+ `Text cursors require an Automerge text field at '${path.map(String).join(".")}'`
799
+ );
800
+ }
801
+ return value;
802
+ }
803
+ function readMockTypedValues(value) {
804
+ if (value === null || typeof value !== "object") {
805
+ return value;
806
+ }
807
+ if (value instanceof Uint8Array) {
808
+ return value.slice();
809
+ }
810
+ if (Array.isArray(value)) {
811
+ return value.map((entry) => readMockTypedValues(entry));
812
+ }
813
+ const record = value;
814
+ if (Object.prototype.hasOwnProperty.call(record, MINDOODB_APP_VALUE_TAG)) {
815
+ switch (record[MINDOODB_APP_VALUE_TAG]) {
816
+ case "atomic":
817
+ if (typeof record.value !== "string") {
818
+ throw new Error('Typed value "atomic" needs a string value');
819
+ }
820
+ return record.value;
821
+ case "counter":
822
+ assertMockCounterAmount(record.value, 'Typed value "counter"');
823
+ return record.value;
824
+ case "timestamp":
825
+ return toMockTimestamp(record.value);
826
+ default:
827
+ throw new Error(
828
+ `Unknown typed value ${JSON.stringify(record[MINDOODB_APP_VALUE_TAG])}; the "${MINDOODB_APP_VALUE_TAG}" key is reserved`
829
+ );
830
+ }
831
+ }
832
+ return Object.fromEntries(
833
+ Object.entries(record).map(([key, entry]) => [key, readMockTypedValues(entry)])
834
+ );
835
+ }
836
+ function toMockTimestamp(value) {
837
+ const date = typeof value === "number" || typeof value === "string" && value.trim() !== "" ? new Date(value) : null;
838
+ if (!date || Number.isNaN(date.getTime())) {
839
+ throw new Error(
840
+ 'Typed value "timestamp" must be epoch milliseconds or an ISO 8601 date-time string'
841
+ );
842
+ }
843
+ return date.toISOString();
844
+ }
845
+ function assertMockCounterAmount(amount, label) {
846
+ if (typeof amount !== "number" || !Number.isSafeInteger(amount)) {
847
+ throw new Error(`${label} must be a safe integer`);
848
+ }
525
849
  }
526
850
  function setValueAtPath(target, path, value) {
527
851
  if (path.length === 0) {
@@ -816,27 +1140,27 @@ function createDefaultWritableAttachmentStream() {
816
1140
  }
817
1141
  };
818
1142
  }
819
- function mockDecryptionKeyId(document) {
820
- if (!document.decryptionKeyId || document.data._encryptFor) {
1143
+ function mockDecryptionKeyId(document2) {
1144
+ if (!document2.decryptionKeyId || document2.data._encryptFor) {
821
1145
  return {};
822
1146
  }
823
- return { decryptionKeyId: document.decryptionKeyId };
1147
+ return { decryptionKeyId: document2.decryptionKeyId };
824
1148
  }
825
- function loadMockAutomergeDocument(document) {
826
- if (document.automergeBinary) {
827
- return Automerge.load(document.automergeBinary);
1149
+ function loadMockAutomergeDocument(document2) {
1150
+ if (document2.automergeBinary) {
1151
+ return Automerge.load(document2.automergeBinary);
828
1152
  }
829
1153
  const initialDoc = Automerge.from({
830
- ...structuredClone(document.data),
831
- body: typeof document.data.body === "string" ? document.data.body : ""
1154
+ ...structuredClone(document2.data),
1155
+ body: typeof document2.data.body === "string" ? document2.data.body : ""
832
1156
  });
833
- document.automergeBinary = Automerge.save(initialDoc);
1157
+ document2.automergeBinary = Automerge.save(initialDoc);
834
1158
  return initialDoc;
835
1159
  }
836
- function persistMockAutomergeDocument(document, automergeDoc) {
837
- document.automergeBinary = Automerge.save(automergeDoc);
838
- document.data = structuredClone(automergeDoc);
839
- document.heads = Automerge.getHeads(automergeDoc);
1160
+ function persistMockAutomergeDocument(document2, automergeDoc) {
1161
+ document2.automergeBinary = Automerge.save(automergeDoc);
1162
+ document2.data = structuredClone(automergeDoc);
1163
+ document2.heads = Automerge.getHeads(automergeDoc);
840
1164
  }
841
1165
  function getFieldValue2(source, field) {
842
1166
  return field.split(".").reduce((current, part) => {
@@ -846,12 +1170,12 @@ function getFieldValue2(source, field) {
846
1170
  return void 0;
847
1171
  }, source);
848
1172
  }
849
- function matchesDocumentFilter(document, filter) {
1173
+ function matchesDocumentFilter(document2, filter) {
850
1174
  if (!filter) {
851
1175
  return true;
852
1176
  }
853
1177
  return Object.entries(filter).every(
854
- ([field, expected]) => getFieldValue2(document.data, field) === expected
1178
+ ([field, expected]) => getFieldValue2(document2.data, field) === expected
855
1179
  );
856
1180
  }
857
1181
  function matchesMockIdPrefix(docId, idPrefix) {
@@ -898,8 +1222,8 @@ function collectMockText(value, depth = 0) {
898
1222
  return "";
899
1223
  }
900
1224
  const parts = [];
901
- for (const item of Array.isArray(value) ? value : Object.values(value)) {
902
- const text = collectMockText(item, depth + 1);
1225
+ for (const item2 of Array.isArray(value) ? value : Object.values(value)) {
1226
+ const text = collectMockText(item2, depth + 1);
903
1227
  if (text.length > 0) {
904
1228
  parts.push(text);
905
1229
  }
@@ -909,14 +1233,14 @@ function collectMockText(value, depth = 0) {
909
1233
  function mockTokenize(text) {
910
1234
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length > 0);
911
1235
  }
912
- function computeMockTextScore(document, clause) {
1236
+ function computeMockTextScore(document2, clause) {
913
1237
  const terms = mockTokenize(clause.query);
914
1238
  if (terms.length === 0) {
915
1239
  return void 0;
916
1240
  }
917
- const haystack = clause.fields ? clause.fields.map((field) => collectMockText(getFieldValue2(document.data, field))).join(" ") : collectMockText(
1241
+ const haystack = clause.fields ? clause.fields.map((field) => collectMockText(getFieldValue2(document2.data, field))).join(" ") : collectMockText(
918
1242
  Object.fromEntries(
919
- Object.entries(document.data).filter(([key]) => !key.startsWith("_"))
1243
+ Object.entries(document2.data).filter(([key]) => !key.startsWith("_"))
920
1244
  )
921
1245
  );
922
1246
  const tokens = mockTokenize(haystack);
@@ -940,22 +1264,22 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
940
1264
  const filter = typeof query?.filter === "string" ? parseMindooDBFormulaBooleanExpression(query.filter) : query?.filter;
941
1265
  const sortBy = query?.sortBy && query.sortBy.length > 0 ? query.sortBy : query?.text ? [{ special: "textScore", direction: "descending" }] : [];
942
1266
  const matches = [];
943
- for (const document of storedDocuments.values()) {
944
- if (document.isDeleted || document.inaccessible) {
1267
+ for (const document2 of storedDocuments.values()) {
1268
+ if (document2.isDeleted || document2.inaccessible) {
945
1269
  continue;
946
1270
  }
947
1271
  let textScore;
948
1272
  if (query?.text) {
949
- textScore = computeMockTextScore(document, query.text);
1273
+ textScore = computeMockTextScore(document2, query.text);
950
1274
  if (textScore === void 0) {
951
1275
  continue;
952
1276
  }
953
1277
  }
954
1278
  const context = {
955
- doc: document.data,
1279
+ doc: document2.data,
956
1280
  values: {},
957
1281
  origin: databaseId,
958
- docId: document.id,
1282
+ docId: document2.id,
959
1283
  variables: {}
960
1284
  };
961
1285
  if (filter && !mockExpressionToBoolean(evaluateExpression2(filter, context))) {
@@ -965,9 +1289,9 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
965
1289
  if (key.special === "textScore") {
966
1290
  return textScore ?? 0;
967
1291
  }
968
- return key.expression ? evaluateExpression2(key.expression, context) : getFieldValue2(document.data, key.field ?? "");
1292
+ return key.expression ? evaluateExpression2(key.expression, context) : getFieldValue2(document2.data, key.field ?? "");
969
1293
  });
970
- matches.push({ document, sortValues, textScore });
1294
+ matches.push({ document: document2, sortValues, textScore });
971
1295
  }
972
1296
  if (sortBy.length > 0) {
973
1297
  matches.sort((left, right) => {
@@ -988,19 +1312,19 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
988
1312
  const offset = Math.max(0, query?.offset ?? 0);
989
1313
  const limit = Math.max(1, query?.limit ?? 200);
990
1314
  const page = matches.slice(offset, offset + limit);
991
- const rows = page.map(({ document, textScore }) => ({
992
- docId: document.id,
993
- fields: projectMockFields(document.data, query?.fields),
994
- lastModified: mockLastModified(document),
1315
+ const rows = page.map(({ document: document2, textScore }) => ({
1316
+ docId: document2.id,
1317
+ fields: projectMockFields(document2.data, query?.fields),
1318
+ lastModified: mockLastModified(document2),
995
1319
  ...textScore === void 0 ? {} : { textScore }
996
1320
  }));
997
1321
  if (query?.include) {
998
1322
  hydrateMockIncludes({
999
1323
  includes: query.include,
1000
- parents: page.map(({ document }, index) => ({
1324
+ parents: page.map(({ document: document2 }, index) => ({
1001
1325
  row: rows[index],
1002
- docId: document.id,
1003
- data: document.data
1326
+ docId: document2.id,
1327
+ data: document2.data
1004
1328
  })),
1005
1329
  parentDatabaseId: databaseId,
1006
1330
  parentDocuments: storedDocuments,
@@ -1014,8 +1338,8 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1014
1338
  function projectMockFields(data, fields) {
1015
1339
  return fields ? Object.fromEntries(fields.map((field) => [field, getFieldValue2(data, field)])) : structuredClone(data);
1016
1340
  }
1017
- function mockLastModified(document) {
1018
- return document.updatedAt ? Date.parse(document.updatedAt) || 0 : 0;
1341
+ function mockLastModified(document2) {
1342
+ return document2.updatedAt ? Date.parse(document2.updatedAt) || 0 : 0;
1019
1343
  }
1020
1344
  var MOCK_MAX_INCLUDE_DEPTH = 3;
1021
1345
  var MOCK_DEFAULT_INCLUDE_LIMIT = 200;
@@ -1124,7 +1448,7 @@ function mockJoinValues(value) {
1124
1448
  return [];
1125
1449
  }
1126
1450
  return (Array.isArray(value) ? value : [value]).filter(
1127
- (item) => item !== void 0 && item !== null
1451
+ (item2) => item2 !== void 0 && item2 !== null
1128
1452
  );
1129
1453
  }
1130
1454
  function validateMockInclude(slot, label, include, depth) {
@@ -1208,35 +1532,35 @@ function hydrateMockIncludes(params) {
1208
1532
  const sortBy = include.sortBy ?? [];
1209
1533
  const childrenByKey = /* @__PURE__ */ new Map();
1210
1534
  if (parentsByKey.size > 0) {
1211
- for (const document of childDocuments.values()) {
1212
- if (document.isDeleted || document.inaccessible) {
1535
+ for (const document2 of childDocuments.values()) {
1536
+ if (document2.isDeleted || document2.inaccessible) {
1213
1537
  continue;
1214
1538
  }
1215
- const childValue = plan.child.kind === "docId" ? document.id : getFieldValue2(document.data, plan.child.path);
1539
+ const childValue = plan.child.kind === "docId" ? document2.id : getFieldValue2(document2.data, plan.child.path);
1216
1540
  const matchedKeys = mockJoinValues(childValue).filter((key) => parentsByKey.has(key));
1217
1541
  if (matchedKeys.length === 0) {
1218
1542
  continue;
1219
1543
  }
1220
1544
  const context = {
1221
- doc: document.data,
1545
+ doc: document2.data,
1222
1546
  values: {},
1223
1547
  origin: childDatabaseId,
1224
- docId: document.id,
1548
+ docId: document2.id,
1225
1549
  variables: {}
1226
1550
  };
1227
1551
  if (plan.residualFilter && !mockExpressionToBoolean(evaluateExpression2(plan.residualFilter, context))) {
1228
1552
  continue;
1229
1553
  }
1230
1554
  const candidate = {
1231
- docId: document.id,
1232
- data: document.data,
1555
+ docId: document2.id,
1556
+ data: document2.data,
1233
1557
  row: {
1234
- docId: document.id,
1235
- fields: projectMockFields(document.data, include.fields),
1236
- lastModified: mockLastModified(document)
1558
+ docId: document2.id,
1559
+ fields: projectMockFields(document2.data, include.fields),
1560
+ lastModified: mockLastModified(document2)
1237
1561
  },
1238
1562
  sortValues: sortBy.map(
1239
- (key) => key.expression ? evaluateExpression2(key.expression, context) : getFieldValue2(document.data, key.field ?? "")
1563
+ (key) => key.expression ? evaluateExpression2(key.expression, context) : getFieldValue2(document2.data, key.field ?? "")
1240
1564
  )
1241
1565
  };
1242
1566
  for (const key of matchedKeys) {
@@ -1665,30 +1989,30 @@ function createDatabaseHandle(definition, peers) {
1665
1989
  const offset = decodeMockListCursor(query?.cursor) + skip;
1666
1990
  const metadataOnly = query?.metadataOnly ?? false;
1667
1991
  const idPrefix = query?.idPrefix;
1668
- const items = Array.from(storedDocuments.values()).filter((document) => !document.inaccessible).filter(
1669
- (document) => status === "all" ? true : status === "deleted" ? document.isDeleted : !document.isDeleted
1670
- ).filter((document) => matchesMockIdPrefix(document.id, idPrefix)).filter(
1671
- (document) => metadataOnly ? true : matchesDocumentFilter(document, query?.filter)
1992
+ const items = Array.from(storedDocuments.values()).filter((document2) => !document2.inaccessible).filter(
1993
+ (document2) => status === "all" ? true : status === "deleted" ? document2.isDeleted : !document2.isDeleted
1994
+ ).filter((document2) => matchesMockIdPrefix(document2.id, idPrefix)).filter(
1995
+ (document2) => metadataOnly ? true : matchesDocumentFilter(document2, query?.filter)
1672
1996
  ).sort((left, right) => left.id.localeCompare(right.id));
1673
- const page = items.slice(offset, offset + limit).map((document) => {
1997
+ const page = items.slice(offset, offset + limit).map((document2) => {
1674
1998
  if (metadataOnly) {
1675
1999
  return {
1676
- id: document.id,
1677
- isDeleted: document.isDeleted
2000
+ id: document2.id,
2001
+ isDeleted: document2.isDeleted
1678
2002
  };
1679
2003
  }
1680
2004
  const projectedData = query?.fields ? Object.fromEntries(
1681
2005
  query.fields.map((field) => [
1682
2006
  field,
1683
- getFieldValue2(document.data, field)
2007
+ getFieldValue2(document2.data, field)
1684
2008
  ])
1685
- ) : document.data;
2009
+ ) : document2.data;
1686
2010
  return {
1687
- id: document.id,
2011
+ id: document2.id,
1688
2012
  data: projectedData,
1689
- attachmentCount: document.attachments?.length ?? 0,
1690
- updatedAt: document.updatedAt,
1691
- isDeleted: status !== "existing" ? document.isDeleted : void 0
2013
+ attachmentCount: document2.attachments?.length ?? 0,
2014
+ updatedAt: document2.updatedAt,
2015
+ isDeleted: status !== "existing" ? document2.isDeleted : void 0
1692
2016
  };
1693
2017
  });
1694
2018
  const nextCursor = offset + page.length < items.length ? String(offset + page.length) : null;
@@ -1704,63 +2028,102 @@ function createDatabaseHandle(definition, peers) {
1704
2028
  },
1705
2029
  async listInaccessible(query) {
1706
2030
  const idPrefix = query?.idPrefix;
1707
- const items = Array.from(storedDocuments.values()).filter((document) => document.inaccessible).filter((document) => matchesMockIdPrefix(document.id, idPrefix)).sort((left, right) => left.id.localeCompare(right.id)).map((document) => ({
1708
- id: document.id,
1709
- createdAt: document.createdAt ?? document.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1710
- decryptionKeyId: document.decryptionKeyId ?? "sealed",
1711
- authorLabel: document.authorLabel
2031
+ const items = Array.from(storedDocuments.values()).filter((document2) => document2.inaccessible).filter((document2) => matchesMockIdPrefix(document2.id, idPrefix)).sort((left, right) => left.id.localeCompare(right.id)).map((document2) => ({
2032
+ id: document2.id,
2033
+ createdAt: document2.createdAt ?? document2.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2034
+ decryptionKeyId: document2.decryptionKeyId ?? "sealed",
2035
+ authorLabel: document2.authorLabel
1712
2036
  }));
1713
2037
  return { items };
1714
2038
  },
1715
2039
  async get(_docId) {
1716
- const document = storedDocuments.get(_docId);
1717
- if (!document || document.isDeleted || document.inaccessible) {
2040
+ const document2 = storedDocuments.get(_docId);
2041
+ if (!document2 || document2.isDeleted || document2.inaccessible) {
1718
2042
  return null;
1719
2043
  }
1720
2044
  return {
1721
- id: document.id,
1722
- data: structuredClone(document.data),
1723
- heads: document.heads ? [...document.heads] : void 0,
1724
- attachments: document.attachments ? structuredClone(document.attachments) : [],
1725
- updatedAt: document.updatedAt,
1726
- ...mockDecryptionKeyId(document)
2045
+ id: document2.id,
2046
+ data: structuredClone(document2.data),
2047
+ heads: document2.heads ? [...document2.heads] : void 0,
2048
+ attachments: document2.attachments ? structuredClone(document2.attachments) : [],
2049
+ updatedAt: document2.updatedAt,
2050
+ ...mockDecryptionKeyId(document2)
1727
2051
  };
1728
2052
  },
1729
2053
  async getRichText(docId, path, _options) {
1730
- const document = storedDocuments.get(docId);
1731
- if (!document || document.isDeleted) {
2054
+ const document2 = storedDocuments.get(docId);
2055
+ if (!document2 || document2.isDeleted) {
1732
2056
  throw new Error(`Document ${docId} was not found.`);
1733
2057
  }
1734
2058
  return {
1735
2059
  path: [...path],
1736
- heads: document.heads ? [...document.heads] : void 0,
1737
- spans: structuredClone(readRichTextSpansAtPath(document.data, path))
2060
+ heads: document2.heads ? [...document2.heads] : void 0,
2061
+ spans: structuredClone(readRichTextSpansAtPath(document2.data, path))
2062
+ };
2063
+ },
2064
+ // Mock cursors store the index they were created at and do not follow
2065
+ // later edits; Haven's Automerge-backed cursors do.
2066
+ async getTextCursors(docId, path, positions, options) {
2067
+ const document2 = storedDocuments.get(docId);
2068
+ if (!document2 || document2.isDeleted) {
2069
+ throw new Error(`Document ${docId} was not found.`);
2070
+ }
2071
+ const text = readMockCursorText(document2.data, path);
2072
+ return {
2073
+ path: [...path],
2074
+ heads: options?.heads ? [...options.heads] : [...document2.heads ?? []],
2075
+ cursors: positions.map((position) => {
2076
+ if (position === "start") return `${MOCK_CURSOR_PREFIX}0`;
2077
+ if (position === "end") return `${MOCK_CURSOR_PREFIX}end`;
2078
+ if (typeof position !== "number" || !Number.isFinite(position)) {
2079
+ throw new Error('Text cursor positions must be numbers, "start" or "end"');
2080
+ }
2081
+ return `${MOCK_CURSOR_PREFIX}${Math.max(0, Math.min(Math.trunc(position), text.length))}`;
2082
+ })
2083
+ };
2084
+ },
2085
+ async resolveTextCursors(docId, path, cursors, options) {
2086
+ const document2 = storedDocuments.get(docId);
2087
+ if (!document2 || document2.isDeleted) {
2088
+ throw new Error(`Document ${docId} was not found.`);
2089
+ }
2090
+ const text = readMockCursorText(document2.data, path);
2091
+ return {
2092
+ path: [...path],
2093
+ heads: options?.heads ? [...options.heads] : [...document2.heads ?? []],
2094
+ positions: cursors.map((cursor) => {
2095
+ if (typeof cursor !== "string" || !cursor.startsWith(MOCK_CURSOR_PREFIX)) {
2096
+ throw new Error(`Unknown text cursor ${String(cursor)}`);
2097
+ }
2098
+ const raw = cursor.slice(MOCK_CURSOR_PREFIX.length);
2099
+ return raw === "end" ? text.length : Math.min(Number(raw), text.length);
2100
+ })
1738
2101
  };
1739
2102
  },
1740
2103
  async getAutomergeSnapshot(docId, _options) {
1741
- const document = storedDocuments.get(docId);
1742
- if (!document || document.isDeleted) {
2104
+ const document2 = storedDocuments.get(docId);
2105
+ if (!document2 || document2.isDeleted) {
1743
2106
  throw new Error(`Document ${docId} was not found.`);
1744
2107
  }
1745
- const automergeDoc = loadMockAutomergeDocument(document);
2108
+ const automergeDoc = loadMockAutomergeDocument(document2);
1746
2109
  return {
1747
2110
  binary: Automerge.save(automergeDoc),
1748
2111
  heads: Automerge.getHeads(automergeDoc)
1749
2112
  };
1750
2113
  },
1751
2114
  async applyAutomergeChanges(docId, patch) {
1752
- const document = storedDocuments.get(docId);
1753
- if (!document || document.isDeleted) {
2115
+ const document2 = storedDocuments.get(docId);
2116
+ if (!document2 || document2.isDeleted) {
1754
2117
  throw new Error(`Document ${docId} was not found.`);
1755
2118
  }
1756
- const currentDoc = loadMockAutomergeDocument(document);
2119
+ const currentDoc = loadMockAutomergeDocument(document2);
1757
2120
  const [mergedDoc] = Automerge.applyChanges(
1758
2121
  currentDoc,
1759
2122
  patch.changes.map((change) => new Uint8Array(change))
1760
2123
  );
1761
- persistMockAutomergeDocument(document, mergedDoc);
1762
- document.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1763
- const mergedHeads = document.heads ? [...document.heads] : [];
2124
+ persistMockAutomergeDocument(document2, mergedDoc);
2125
+ document2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2126
+ const mergedHeads = document2.heads ? [...document2.heads] : [];
1764
2127
  let changesSince;
1765
2128
  if (patch.replicaHeads?.length) {
1766
2129
  const missingChanges = Automerge.getChangesSince(
@@ -1774,17 +2137,24 @@ function createDatabaseHandle(definition, peers) {
1774
2137
  }
1775
2138
  return {
1776
2139
  document: {
1777
- id: document.id,
1778
- data: structuredClone(document.data),
2140
+ id: document2.id,
2141
+ data: structuredClone(document2.data),
1779
2142
  heads: mergedHeads,
1780
- attachments: document.attachments ? structuredClone(document.attachments) : [],
1781
- updatedAt: document.updatedAt,
1782
- ...mockDecryptionKeyId(document)
2143
+ attachments: document2.attachments ? structuredClone(document2.attachments) : [],
2144
+ updatedAt: document2.updatedAt,
2145
+ ...mockDecryptionKeyId(document2)
1783
2146
  },
1784
2147
  heads: mergedHeads,
1785
2148
  changesSince
1786
2149
  };
1787
2150
  },
2151
+ async applyAutomergeChangesBatch(items) {
2152
+ const results = [];
2153
+ for (const item2 of items) {
2154
+ results.push(await this.applyAutomergeChanges(item2.docId, item2.patch));
2155
+ }
2156
+ return results;
2157
+ },
1788
2158
  async create(input) {
1789
2159
  if (input.id !== void 0 && input.idPrefix !== void 0) {
1790
2160
  throw new Error("documents.create: id and idPrefix are mutually exclusive");
@@ -1831,7 +2201,7 @@ function createDatabaseHandle(definition, peers) {
1831
2201
  const created = {
1832
2202
  id,
1833
2203
  data: {
1834
- ...input.set,
2204
+ ...readMockTypedValues(input.set ?? {}),
1835
2205
  ...Array.isArray(input.recipients) ? { _encryptFor: mockEncryptForMap(input.recipients) } : {}
1836
2206
  },
1837
2207
  heads: [`mock-head-${++changeCounter}`],
@@ -2119,6 +2489,7 @@ function createDatabaseHandle(definition, peers) {
2119
2489
  const methods = definition.methods ?? {};
2120
2490
  let fulltextSetup = definition.fulltextSetup ?? null;
2121
2491
  let extractionSetup = definition.extractionSetup ?? null;
2492
+ let summarySetup = definition.summarySetup ?? null;
2122
2493
  const databaseHandle = {
2123
2494
  async info() {
2124
2495
  return {
@@ -2157,15 +2528,21 @@ function createDatabaseHandle(definition, peers) {
2157
2528
  },
2158
2529
  async setExtractionSetup(config) {
2159
2530
  extractionSetup = config === null ? null : { ...config };
2531
+ },
2532
+ async getSummarySetup() {
2533
+ return summarySetup === null ? null : structuredClone(summarySetup);
2534
+ },
2535
+ async setSummarySetup(config) {
2536
+ summarySetup = config === null ? null : structuredClone(config);
2160
2537
  }
2161
2538
  };
2162
2539
  return {
2163
2540
  handle: databaseHandle,
2164
- listViewDocuments: () => Array.from(storedDocuments.values()).filter((document) => !document.isDeleted && !document.inaccessible).map((document) => ({
2541
+ listViewDocuments: () => Array.from(storedDocuments.values()).filter((document2) => !document2.isDeleted && !document2.inaccessible).map((document2) => ({
2165
2542
  origin: definition.info.id,
2166
- docId: document.id,
2167
- data: structuredClone(document.data),
2168
- createdAt: document.updatedAt ?? null
2543
+ docId: document2.id,
2544
+ data: structuredClone(document2.data),
2545
+ createdAt: document2.updatedAt ?? null
2169
2546
  })),
2170
2547
  documents: storedDocuments,
2171
2548
  notifyLiveQueries: refreshLiveQueries
@@ -2174,6 +2551,8 @@ function createDatabaseHandle(definition, peers) {
2174
2551
  function createMockSessionState(options = {}) {
2175
2552
  let launchContext = createDefaultLaunchContext(options.launchContext);
2176
2553
  const themeListeners = /* @__PURE__ */ new Set();
2554
+ const hostFocusListeners = /* @__PURE__ */ new Set();
2555
+ let hostFocused = false;
2177
2556
  const viewportListeners = /* @__PURE__ */ new Set();
2178
2557
  const uiPreferencesListeners = /* @__PURE__ */ new Set();
2179
2558
  const localeListeners = /* @__PURE__ */ new Set();
@@ -2465,6 +2844,23 @@ function createMockSessionState(options = {}) {
2465
2844
  beforeCloseListeners.delete(listener);
2466
2845
  };
2467
2846
  },
2847
+ async requestHostFocus() {
2848
+ hostFocused = true;
2849
+ hostFocusListeners.forEach((listener) => listener(true));
2850
+ },
2851
+ async hasHostFocus() {
2852
+ return hostFocused;
2853
+ },
2854
+ onHostFocusChange(listener) {
2855
+ hostFocusListeners.add(listener);
2856
+ return () => {
2857
+ hostFocusListeners.delete(listener);
2858
+ };
2859
+ },
2860
+ async notify(input) {
2861
+ const id = input.id?.trim() || crypto.randomUUID();
2862
+ return { id };
2863
+ },
2468
2864
  async disconnect() {
2469
2865
  await options.onDisconnect?.();
2470
2866
  }
@@ -2527,6 +2923,10 @@ function createMockSessionState(options = {}) {
2527
2923
  async emitBeforeClose() {
2528
2924
  await Promise.all([...beforeCloseListeners].map(async (listener) => listener()));
2529
2925
  },
2926
+ setHostFocused(focused) {
2927
+ hostFocused = focused;
2928
+ hostFocusListeners.forEach((listener) => listener(focused));
2929
+ },
2530
2930
  resolveDrag(result) {
2531
2931
  activeDragResolve?.(result);
2532
2932
  activeDragResolve = null;
@@ -2552,6 +2952,9 @@ function createMockMindooDBAppSession(options = {}) {
2552
2952
  emitUiPreferencesChange: state.emitUiPreferencesChange,
2553
2953
  emitLocaleChange: state.emitLocaleChange,
2554
2954
  emitBeforeClose: state.emitBeforeClose,
2955
+ emitHostFocusChange(focused) {
2956
+ state.setHostFocused(focused);
2957
+ },
2555
2958
  resolveDrag: state.resolveDrag,
2556
2959
  getDragProfile: state.getDragProfile
2557
2960
  };
@@ -2581,31 +2984,35 @@ function createFakeBridgeHost(options = {}) {
2581
2984
  );
2582
2985
  let previousWindowValue = typeof window === "undefined" ? void 0 : window;
2583
2986
  let installed = false;
2987
+ function acceptConnection(message, transfer) {
2988
+ const payload = message;
2989
+ if (!payload || payload.protocol !== PROTOCOL || payload.type !== "mindoodb-app:connect") {
2990
+ return false;
2991
+ }
2992
+ if (payload.launchId !== state.getLaunchContext().launchId) {
2993
+ return false;
2994
+ }
2995
+ const port = transfer?.[0];
2996
+ if (!port) {
2997
+ throw new Error(
2998
+ "Expected the bridge connection to transfer a MessagePort."
2999
+ );
3000
+ }
3001
+ connectedPorts.add(port);
3002
+ port.addEventListener("message", (event) => {
3003
+ void handlePortMessage(port, event.data);
3004
+ });
3005
+ port.start();
3006
+ const connected = {
3007
+ protocol: PROTOCOL,
3008
+ type: "mindoodb-app:connected"
3009
+ };
3010
+ port.postMessage(connected);
3011
+ return true;
3012
+ }
2584
3013
  const hostWindow = {
2585
3014
  postMessage(message, _targetOrigin, transfer) {
2586
- const payload = message;
2587
- if (!payload || payload.protocol !== PROTOCOL || payload.type !== "mindoodb-app:connect") {
2588
- return;
2589
- }
2590
- if (payload.launchId !== state.getLaunchContext().launchId) {
2591
- return;
2592
- }
2593
- const port = transfer?.[0];
2594
- if (!port) {
2595
- throw new Error(
2596
- "Expected the bridge connection to transfer a MessagePort."
2597
- );
2598
- }
2599
- connectedPorts.add(port);
2600
- port.addEventListener("message", (event) => {
2601
- void handlePortMessage(port, event.data);
2602
- });
2603
- port.start();
2604
- const connected = {
2605
- protocol: PROTOCOL,
2606
- type: "mindoodb-app:connected"
2607
- };
2608
- port.postMessage(connected);
3015
+ acceptConnection(message, transfer);
2609
3016
  }
2610
3017
  };
2611
3018
  function getWindowSearch() {
@@ -2725,6 +3132,8 @@ function createFakeBridgeHost(options = {}) {
2725
3132
  return state.getLicensedProducts();
2726
3133
  case "session.listDatabases":
2727
3134
  return state.listDatabaseInfos();
3135
+ case "session.listDocumentsSinceViewCursor":
3136
+ return { items: [], nextCursor: null };
2728
3137
  case "session.openDatabase":
2729
3138
  state.getDatabase(String(params.databaseId));
2730
3139
  return { ok: true };
@@ -2755,6 +3164,15 @@ function createFakeBridgeHost(options = {}) {
2755
3164
  case "menus.hide":
2756
3165
  await state.session.menus.hide();
2757
3166
  return { ok: true };
3167
+ case "session.requestHostFocus":
3168
+ await state.session.requestHostFocus();
3169
+ return null;
3170
+ case "session.hasHostFocus":
3171
+ return { focused: await state.session.hasHostFocus() };
3172
+ case "notifications.show":
3173
+ return await state.session.notify(
3174
+ request.params
3175
+ );
2758
3176
  case "session.disconnect":
2759
3177
  await state.session.disconnect();
2760
3178
  return { ok: true };
@@ -2799,6 +3217,15 @@ function createFakeBridgeHost(options = {}) {
2799
3217
  params.config ?? null
2800
3218
  );
2801
3219
  return { ok: true };
3220
+ case "database.getSummarySetup":
3221
+ return {
3222
+ config: await state.getDatabase(String(params.databaseId)).getSummarySetup()
3223
+ };
3224
+ case "database.setSummarySetup":
3225
+ await state.getDatabase(String(params.databaseId)).setSummarySetup(
3226
+ params.config ?? null
3227
+ );
3228
+ return { ok: true };
2802
3229
  case "documents.query":
2803
3230
  return await state.getDatabase(String(params.databaseId)).documents.query(
2804
3231
  params.query
@@ -2844,6 +3271,23 @@ function createFakeBridgeHost(options = {}) {
2844
3271
  String(params.docId),
2845
3272
  params.path
2846
3273
  );
3274
+ case "documents.textCursors.get":
3275
+ return await state.getDatabase(String(params.databaseId)).documents.getTextCursors(
3276
+ String(params.docId),
3277
+ params.path,
3278
+ params.positions,
3279
+ {
3280
+ heads: params.heads,
3281
+ move: params.move
3282
+ }
3283
+ );
3284
+ case "documents.textCursors.resolve":
3285
+ return await state.getDatabase(String(params.databaseId)).documents.resolveTextCursors(
3286
+ String(params.docId),
3287
+ params.path,
3288
+ params.cursors,
3289
+ { heads: params.heads }
3290
+ );
2847
3291
  case "documents.automerge.getSnapshot":
2848
3292
  return await state.getDatabase(String(params.databaseId)).documents.getAutomergeSnapshot(
2849
3293
  String(params.docId),
@@ -2854,6 +3298,18 @@ function createFakeBridgeHost(options = {}) {
2854
3298
  String(params.docId),
2855
3299
  params.patch
2856
3300
  );
3301
+ case "documents.automerge.applyChangesBatch":
3302
+ return await state.getDatabase(String(params.databaseId)).documents.applyAutomergeChangesBatch(
3303
+ Array.isArray(params.items) ? params.items : []
3304
+ );
3305
+ case "documents.createMany":
3306
+ return await state.getDatabase(String(params.databaseId)).documents.createMany(
3307
+ Array.isArray(params.inputs) ? params.inputs : []
3308
+ );
3309
+ case "documents.deleteMany":
3310
+ return await state.getDatabase(String(params.databaseId)).documents.deleteMany(Array.isArray(params.docIds) ? params.docIds.map(String) : []);
3311
+ case "databases.info":
3312
+ return await state.getDatabase(String(params.databaseId)).info();
2857
3313
  case "documents.create":
2858
3314
  return await state.getDatabase(String(params.databaseId)).documents.create(
2859
3315
  params.input
@@ -3205,8 +3661,19 @@ function createFakeBridgeHost(options = {}) {
3205
3661
  beforeCloseWaiters.get(message.closeId)?.();
3206
3662
  return;
3207
3663
  }
3664
+ if (message.kind === "workspace-focus-requested") {
3665
+ state.setHostFocused(true);
3666
+ const payload = {
3667
+ protocol: PROTOCOL,
3668
+ kind: "host-focus-changed",
3669
+ focused: true
3670
+ };
3671
+ port.postMessage(payload);
3672
+ return;
3673
+ }
3208
3674
  if (message.kind === "request") {
3209
3675
  requests.push(message);
3676
+ options.onRequest?.(message);
3210
3677
  try {
3211
3678
  const customHandler = customRequestHandlers.get(message.method);
3212
3679
  if (customHandler) {
@@ -3221,7 +3688,7 @@ function createFakeBridgeHost(options = {}) {
3221
3688
  const builtinResult = await handleBuiltinRequest(message);
3222
3689
  if (builtinResult === void 0 && message.method !== "viewNavigators.dispose") {
3223
3690
  postRpcError(port, message.id, {
3224
- code: "unsupported-method",
3691
+ code: "method-not-found",
3225
3692
  message: `No fake bridge handler is configured for ${message.method}.`
3226
3693
  });
3227
3694
  return;
@@ -3269,8 +3736,13 @@ function createFakeBridgeHost(options = {}) {
3269
3736
  return state.proposedApps;
3270
3737
  },
3271
3738
  install,
3739
+ acceptConnection,
3272
3740
  dispose() {
3273
3741
  restoreWindow();
3742
+ connectedPorts.forEach((port) => {
3743
+ port.close();
3744
+ });
3745
+ connectedPorts.clear();
3274
3746
  viewSessions.clear();
3275
3747
  readStreams.clear();
3276
3748
  writeStreams.clear();
@@ -3315,6 +3787,15 @@ function createFakeBridgeHost(options = {}) {
3315
3787
  };
3316
3788
  connectedPorts.forEach((port) => port.postMessage(payload));
3317
3789
  },
3790
+ emitHostFocusChange(focused) {
3791
+ state.setHostFocused(focused);
3792
+ const payload = {
3793
+ protocol: PROTOCOL,
3794
+ kind: "host-focus-changed",
3795
+ focused
3796
+ };
3797
+ connectedPorts.forEach((port) => port.postMessage(payload));
3798
+ },
3318
3799
  emitQueryResult(subscriptionId, result) {
3319
3800
  const payload = {
3320
3801
  protocol: PROTOCOL,
@@ -3372,9 +3853,13 @@ function createFakeBridgeHost(options = {}) {
3372
3853
  return controller;
3373
3854
  }
3374
3855
  export {
3856
+ capabilitiesForDefinitionPermissions,
3857
+ createBrowserTestHost,
3375
3858
  createEvaluatingViewNavigator,
3376
3859
  createFakeBridgeHost,
3377
3860
  createMockMindooDBAppBridge,
3378
3861
  createMockMindooDBAppSession,
3379
- loadMindoodbForTesting
3862
+ loadMindoodbForTesting,
3863
+ mockDatabasesFromDefinition,
3864
+ mountHavenTestHost
3380
3865
  };