mindoodb-app-sdk 0.0.51 → 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,7 +1,7 @@
1
1
  import {
2
2
  MINDOODB_APP_VALUE_TAG,
3
3
  releaseMindooDBAppBridgeSessions
4
- } from "../chunk-R76XXRJ6.js";
4
+ } from "../chunk-6NTVTL6G.js";
5
5
 
6
6
  // src/testing/index.ts
7
7
  import * as Automerge from "@automerge/automerge";
@@ -29,10 +29,10 @@ function toFormulaBoolean(value) {
29
29
  }
30
30
  return Boolean(value);
31
31
  }
32
- function matchesFilter(mindoodb, definition, document) {
32
+ function matchesFilter(mindoodb, definition, document2) {
33
33
  void mindoodb;
34
34
  if (!definition.filter) return true;
35
- const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document;
35
+ const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } = document2;
36
36
  return toFormulaBoolean(
37
37
  evaluateExpression(definition.filter.expression, {
38
38
  doc,
@@ -47,8 +47,8 @@ function matchesFilter(mindoodb, definition, document) {
47
47
  })
48
48
  );
49
49
  }
50
- function computeColumnValues(definition, document) {
51
- 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;
52
52
  const values = {};
53
53
  for (const [key, value] of Object.entries(doc)) {
54
54
  if (!key.startsWith("_")) values[key] = value;
@@ -172,13 +172,13 @@ async function buildVirtualView(mindoodb, definition, documents, categorizationS
172
172
  }
173
173
  const view = await builder.build();
174
174
  const changes = /* @__PURE__ */ new Map();
175
- for (const document of documents) {
176
- if (!matchesFilter(mindoodb, definition, document)) {
175
+ for (const document2 of documents) {
176
+ if (!matchesFilter(mindoodb, definition, document2)) {
177
177
  continue;
178
178
  }
179
- const change = changes.get(document.origin) ?? new mindoodb.VirtualViewDataChange(document.origin);
180
- change.addEntry(document.docId, computeColumnValues(definition, document));
181
- 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);
182
182
  }
183
183
  for (const change of changes.values()) {
184
184
  if (change.hasChanges()) view.applyChanges(change);
@@ -454,6 +454,249 @@ async function createEvaluatingViewNavigator(input, documents) {
454
454
  return wrapNavigator(input.definition, navigator);
455
455
  }
456
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
+
457
700
  // src/testing/index.ts
458
701
  var PROTOCOL = "mindoodb-app-bridge";
459
702
  var MOCK_SIGNING_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAmockmockmockmockmockmockmockmockmockmockmoc=\n-----END PUBLIC KEY-----\n";
@@ -897,27 +1140,27 @@ function createDefaultWritableAttachmentStream() {
897
1140
  }
898
1141
  };
899
1142
  }
900
- function mockDecryptionKeyId(document) {
901
- if (!document.decryptionKeyId || document.data._encryptFor) {
1143
+ function mockDecryptionKeyId(document2) {
1144
+ if (!document2.decryptionKeyId || document2.data._encryptFor) {
902
1145
  return {};
903
1146
  }
904
- return { decryptionKeyId: document.decryptionKeyId };
1147
+ return { decryptionKeyId: document2.decryptionKeyId };
905
1148
  }
906
- function loadMockAutomergeDocument(document) {
907
- if (document.automergeBinary) {
908
- return Automerge.load(document.automergeBinary);
1149
+ function loadMockAutomergeDocument(document2) {
1150
+ if (document2.automergeBinary) {
1151
+ return Automerge.load(document2.automergeBinary);
909
1152
  }
910
1153
  const initialDoc = Automerge.from({
911
- ...structuredClone(document.data),
912
- body: typeof document.data.body === "string" ? document.data.body : ""
1154
+ ...structuredClone(document2.data),
1155
+ body: typeof document2.data.body === "string" ? document2.data.body : ""
913
1156
  });
914
- document.automergeBinary = Automerge.save(initialDoc);
1157
+ document2.automergeBinary = Automerge.save(initialDoc);
915
1158
  return initialDoc;
916
1159
  }
917
- function persistMockAutomergeDocument(document, automergeDoc) {
918
- document.automergeBinary = Automerge.save(automergeDoc);
919
- document.data = structuredClone(automergeDoc);
920
- 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);
921
1164
  }
922
1165
  function getFieldValue2(source, field) {
923
1166
  return field.split(".").reduce((current, part) => {
@@ -927,12 +1170,12 @@ function getFieldValue2(source, field) {
927
1170
  return void 0;
928
1171
  }, source);
929
1172
  }
930
- function matchesDocumentFilter(document, filter) {
1173
+ function matchesDocumentFilter(document2, filter) {
931
1174
  if (!filter) {
932
1175
  return true;
933
1176
  }
934
1177
  return Object.entries(filter).every(
935
- ([field, expected]) => getFieldValue2(document.data, field) === expected
1178
+ ([field, expected]) => getFieldValue2(document2.data, field) === expected
936
1179
  );
937
1180
  }
938
1181
  function matchesMockIdPrefix(docId, idPrefix) {
@@ -979,8 +1222,8 @@ function collectMockText(value, depth = 0) {
979
1222
  return "";
980
1223
  }
981
1224
  const parts = [];
982
- for (const item of Array.isArray(value) ? value : Object.values(value)) {
983
- 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);
984
1227
  if (text.length > 0) {
985
1228
  parts.push(text);
986
1229
  }
@@ -990,14 +1233,14 @@ function collectMockText(value, depth = 0) {
990
1233
  function mockTokenize(text) {
991
1234
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length > 0);
992
1235
  }
993
- function computeMockTextScore(document, clause) {
1236
+ function computeMockTextScore(document2, clause) {
994
1237
  const terms = mockTokenize(clause.query);
995
1238
  if (terms.length === 0) {
996
1239
  return void 0;
997
1240
  }
998
- 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(
999
1242
  Object.fromEntries(
1000
- Object.entries(document.data).filter(([key]) => !key.startsWith("_"))
1243
+ Object.entries(document2.data).filter(([key]) => !key.startsWith("_"))
1001
1244
  )
1002
1245
  );
1003
1246
  const tokens = mockTokenize(haystack);
@@ -1021,22 +1264,22 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1021
1264
  const filter = typeof query?.filter === "string" ? parseMindooDBFormulaBooleanExpression(query.filter) : query?.filter;
1022
1265
  const sortBy = query?.sortBy && query.sortBy.length > 0 ? query.sortBy : query?.text ? [{ special: "textScore", direction: "descending" }] : [];
1023
1266
  const matches = [];
1024
- for (const document of storedDocuments.values()) {
1025
- if (document.isDeleted || document.inaccessible) {
1267
+ for (const document2 of storedDocuments.values()) {
1268
+ if (document2.isDeleted || document2.inaccessible) {
1026
1269
  continue;
1027
1270
  }
1028
1271
  let textScore;
1029
1272
  if (query?.text) {
1030
- textScore = computeMockTextScore(document, query.text);
1273
+ textScore = computeMockTextScore(document2, query.text);
1031
1274
  if (textScore === void 0) {
1032
1275
  continue;
1033
1276
  }
1034
1277
  }
1035
1278
  const context = {
1036
- doc: document.data,
1279
+ doc: document2.data,
1037
1280
  values: {},
1038
1281
  origin: databaseId,
1039
- docId: document.id,
1282
+ docId: document2.id,
1040
1283
  variables: {}
1041
1284
  };
1042
1285
  if (filter && !mockExpressionToBoolean(evaluateExpression2(filter, context))) {
@@ -1046,9 +1289,9 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1046
1289
  if (key.special === "textScore") {
1047
1290
  return textScore ?? 0;
1048
1291
  }
1049
- 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 ?? "");
1050
1293
  });
1051
- matches.push({ document, sortValues, textScore });
1294
+ matches.push({ document: document2, sortValues, textScore });
1052
1295
  }
1053
1296
  if (sortBy.length > 0) {
1054
1297
  matches.sort((left, right) => {
@@ -1069,19 +1312,19 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1069
1312
  const offset = Math.max(0, query?.offset ?? 0);
1070
1313
  const limit = Math.max(1, query?.limit ?? 200);
1071
1314
  const page = matches.slice(offset, offset + limit);
1072
- const rows = page.map(({ document, textScore }) => ({
1073
- docId: document.id,
1074
- fields: projectMockFields(document.data, query?.fields),
1075
- 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),
1076
1319
  ...textScore === void 0 ? {} : { textScore }
1077
1320
  }));
1078
1321
  if (query?.include) {
1079
1322
  hydrateMockIncludes({
1080
1323
  includes: query.include,
1081
- parents: page.map(({ document }, index) => ({
1324
+ parents: page.map(({ document: document2 }, index) => ({
1082
1325
  row: rows[index],
1083
- docId: document.id,
1084
- data: document.data
1326
+ docId: document2.id,
1327
+ data: document2.data
1085
1328
  })),
1086
1329
  parentDatabaseId: databaseId,
1087
1330
  parentDocuments: storedDocuments,
@@ -1095,8 +1338,8 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1095
1338
  function projectMockFields(data, fields) {
1096
1339
  return fields ? Object.fromEntries(fields.map((field) => [field, getFieldValue2(data, field)])) : structuredClone(data);
1097
1340
  }
1098
- function mockLastModified(document) {
1099
- return document.updatedAt ? Date.parse(document.updatedAt) || 0 : 0;
1341
+ function mockLastModified(document2) {
1342
+ return document2.updatedAt ? Date.parse(document2.updatedAt) || 0 : 0;
1100
1343
  }
1101
1344
  var MOCK_MAX_INCLUDE_DEPTH = 3;
1102
1345
  var MOCK_DEFAULT_INCLUDE_LIMIT = 200;
@@ -1205,7 +1448,7 @@ function mockJoinValues(value) {
1205
1448
  return [];
1206
1449
  }
1207
1450
  return (Array.isArray(value) ? value : [value]).filter(
1208
- (item) => item !== void 0 && item !== null
1451
+ (item2) => item2 !== void 0 && item2 !== null
1209
1452
  );
1210
1453
  }
1211
1454
  function validateMockInclude(slot, label, include, depth) {
@@ -1289,35 +1532,35 @@ function hydrateMockIncludes(params) {
1289
1532
  const sortBy = include.sortBy ?? [];
1290
1533
  const childrenByKey = /* @__PURE__ */ new Map();
1291
1534
  if (parentsByKey.size > 0) {
1292
- for (const document of childDocuments.values()) {
1293
- if (document.isDeleted || document.inaccessible) {
1535
+ for (const document2 of childDocuments.values()) {
1536
+ if (document2.isDeleted || document2.inaccessible) {
1294
1537
  continue;
1295
1538
  }
1296
- 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);
1297
1540
  const matchedKeys = mockJoinValues(childValue).filter((key) => parentsByKey.has(key));
1298
1541
  if (matchedKeys.length === 0) {
1299
1542
  continue;
1300
1543
  }
1301
1544
  const context = {
1302
- doc: document.data,
1545
+ doc: document2.data,
1303
1546
  values: {},
1304
1547
  origin: childDatabaseId,
1305
- docId: document.id,
1548
+ docId: document2.id,
1306
1549
  variables: {}
1307
1550
  };
1308
1551
  if (plan.residualFilter && !mockExpressionToBoolean(evaluateExpression2(plan.residualFilter, context))) {
1309
1552
  continue;
1310
1553
  }
1311
1554
  const candidate = {
1312
- docId: document.id,
1313
- data: document.data,
1555
+ docId: document2.id,
1556
+ data: document2.data,
1314
1557
  row: {
1315
- docId: document.id,
1316
- fields: projectMockFields(document.data, include.fields),
1317
- lastModified: mockLastModified(document)
1558
+ docId: document2.id,
1559
+ fields: projectMockFields(document2.data, include.fields),
1560
+ lastModified: mockLastModified(document2)
1318
1561
  },
1319
1562
  sortValues: sortBy.map(
1320
- (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 ?? "")
1321
1564
  )
1322
1565
  };
1323
1566
  for (const key of matchedKeys) {
@@ -1746,30 +1989,30 @@ function createDatabaseHandle(definition, peers) {
1746
1989
  const offset = decodeMockListCursor(query?.cursor) + skip;
1747
1990
  const metadataOnly = query?.metadataOnly ?? false;
1748
1991
  const idPrefix = query?.idPrefix;
1749
- const items = Array.from(storedDocuments.values()).filter((document) => !document.inaccessible).filter(
1750
- (document) => status === "all" ? true : status === "deleted" ? document.isDeleted : !document.isDeleted
1751
- ).filter((document) => matchesMockIdPrefix(document.id, idPrefix)).filter(
1752
- (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)
1753
1996
  ).sort((left, right) => left.id.localeCompare(right.id));
1754
- const page = items.slice(offset, offset + limit).map((document) => {
1997
+ const page = items.slice(offset, offset + limit).map((document2) => {
1755
1998
  if (metadataOnly) {
1756
1999
  return {
1757
- id: document.id,
1758
- isDeleted: document.isDeleted
2000
+ id: document2.id,
2001
+ isDeleted: document2.isDeleted
1759
2002
  };
1760
2003
  }
1761
2004
  const projectedData = query?.fields ? Object.fromEntries(
1762
2005
  query.fields.map((field) => [
1763
2006
  field,
1764
- getFieldValue2(document.data, field)
2007
+ getFieldValue2(document2.data, field)
1765
2008
  ])
1766
- ) : document.data;
2009
+ ) : document2.data;
1767
2010
  return {
1768
- id: document.id,
2011
+ id: document2.id,
1769
2012
  data: projectedData,
1770
- attachmentCount: document.attachments?.length ?? 0,
1771
- updatedAt: document.updatedAt,
1772
- 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
1773
2016
  };
1774
2017
  });
1775
2018
  const nextCursor = offset + page.length < items.length ? String(offset + page.length) : null;
@@ -1785,50 +2028,50 @@ function createDatabaseHandle(definition, peers) {
1785
2028
  },
1786
2029
  async listInaccessible(query) {
1787
2030
  const idPrefix = query?.idPrefix;
1788
- 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) => ({
1789
- id: document.id,
1790
- createdAt: document.createdAt ?? document.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1791
- decryptionKeyId: document.decryptionKeyId ?? "sealed",
1792
- 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
1793
2036
  }));
1794
2037
  return { items };
1795
2038
  },
1796
2039
  async get(_docId) {
1797
- const document = storedDocuments.get(_docId);
1798
- if (!document || document.isDeleted || document.inaccessible) {
2040
+ const document2 = storedDocuments.get(_docId);
2041
+ if (!document2 || document2.isDeleted || document2.inaccessible) {
1799
2042
  return null;
1800
2043
  }
1801
2044
  return {
1802
- id: document.id,
1803
- data: structuredClone(document.data),
1804
- heads: document.heads ? [...document.heads] : void 0,
1805
- attachments: document.attachments ? structuredClone(document.attachments) : [],
1806
- updatedAt: document.updatedAt,
1807
- ...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)
1808
2051
  };
1809
2052
  },
1810
2053
  async getRichText(docId, path, _options) {
1811
- const document = storedDocuments.get(docId);
1812
- if (!document || document.isDeleted) {
2054
+ const document2 = storedDocuments.get(docId);
2055
+ if (!document2 || document2.isDeleted) {
1813
2056
  throw new Error(`Document ${docId} was not found.`);
1814
2057
  }
1815
2058
  return {
1816
2059
  path: [...path],
1817
- heads: document.heads ? [...document.heads] : void 0,
1818
- spans: structuredClone(readRichTextSpansAtPath(document.data, path))
2060
+ heads: document2.heads ? [...document2.heads] : void 0,
2061
+ spans: structuredClone(readRichTextSpansAtPath(document2.data, path))
1819
2062
  };
1820
2063
  },
1821
2064
  // Mock cursors store the index they were created at and do not follow
1822
2065
  // later edits; Haven's Automerge-backed cursors do.
1823
2066
  async getTextCursors(docId, path, positions, options) {
1824
- const document = storedDocuments.get(docId);
1825
- if (!document || document.isDeleted) {
2067
+ const document2 = storedDocuments.get(docId);
2068
+ if (!document2 || document2.isDeleted) {
1826
2069
  throw new Error(`Document ${docId} was not found.`);
1827
2070
  }
1828
- const text = readMockCursorText(document.data, path);
2071
+ const text = readMockCursorText(document2.data, path);
1829
2072
  return {
1830
2073
  path: [...path],
1831
- heads: options?.heads ? [...options.heads] : [...document.heads ?? []],
2074
+ heads: options?.heads ? [...options.heads] : [...document2.heads ?? []],
1832
2075
  cursors: positions.map((position) => {
1833
2076
  if (position === "start") return `${MOCK_CURSOR_PREFIX}0`;
1834
2077
  if (position === "end") return `${MOCK_CURSOR_PREFIX}end`;
@@ -1840,14 +2083,14 @@ function createDatabaseHandle(definition, peers) {
1840
2083
  };
1841
2084
  },
1842
2085
  async resolveTextCursors(docId, path, cursors, options) {
1843
- const document = storedDocuments.get(docId);
1844
- if (!document || document.isDeleted) {
2086
+ const document2 = storedDocuments.get(docId);
2087
+ if (!document2 || document2.isDeleted) {
1845
2088
  throw new Error(`Document ${docId} was not found.`);
1846
2089
  }
1847
- const text = readMockCursorText(document.data, path);
2090
+ const text = readMockCursorText(document2.data, path);
1848
2091
  return {
1849
2092
  path: [...path],
1850
- heads: options?.heads ? [...options.heads] : [...document.heads ?? []],
2093
+ heads: options?.heads ? [...options.heads] : [...document2.heads ?? []],
1851
2094
  positions: cursors.map((cursor) => {
1852
2095
  if (typeof cursor !== "string" || !cursor.startsWith(MOCK_CURSOR_PREFIX)) {
1853
2096
  throw new Error(`Unknown text cursor ${String(cursor)}`);
@@ -1858,29 +2101,29 @@ function createDatabaseHandle(definition, peers) {
1858
2101
  };
1859
2102
  },
1860
2103
  async getAutomergeSnapshot(docId, _options) {
1861
- const document = storedDocuments.get(docId);
1862
- if (!document || document.isDeleted) {
2104
+ const document2 = storedDocuments.get(docId);
2105
+ if (!document2 || document2.isDeleted) {
1863
2106
  throw new Error(`Document ${docId} was not found.`);
1864
2107
  }
1865
- const automergeDoc = loadMockAutomergeDocument(document);
2108
+ const automergeDoc = loadMockAutomergeDocument(document2);
1866
2109
  return {
1867
2110
  binary: Automerge.save(automergeDoc),
1868
2111
  heads: Automerge.getHeads(automergeDoc)
1869
2112
  };
1870
2113
  },
1871
2114
  async applyAutomergeChanges(docId, patch) {
1872
- const document = storedDocuments.get(docId);
1873
- if (!document || document.isDeleted) {
2115
+ const document2 = storedDocuments.get(docId);
2116
+ if (!document2 || document2.isDeleted) {
1874
2117
  throw new Error(`Document ${docId} was not found.`);
1875
2118
  }
1876
- const currentDoc = loadMockAutomergeDocument(document);
2119
+ const currentDoc = loadMockAutomergeDocument(document2);
1877
2120
  const [mergedDoc] = Automerge.applyChanges(
1878
2121
  currentDoc,
1879
2122
  patch.changes.map((change) => new Uint8Array(change))
1880
2123
  );
1881
- persistMockAutomergeDocument(document, mergedDoc);
1882
- document.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1883
- const mergedHeads = document.heads ? [...document.heads] : [];
2124
+ persistMockAutomergeDocument(document2, mergedDoc);
2125
+ document2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2126
+ const mergedHeads = document2.heads ? [...document2.heads] : [];
1884
2127
  let changesSince;
1885
2128
  if (patch.replicaHeads?.length) {
1886
2129
  const missingChanges = Automerge.getChangesSince(
@@ -1894,12 +2137,12 @@ function createDatabaseHandle(definition, peers) {
1894
2137
  }
1895
2138
  return {
1896
2139
  document: {
1897
- id: document.id,
1898
- data: structuredClone(document.data),
2140
+ id: document2.id,
2141
+ data: structuredClone(document2.data),
1899
2142
  heads: mergedHeads,
1900
- attachments: document.attachments ? structuredClone(document.attachments) : [],
1901
- updatedAt: document.updatedAt,
1902
- ...mockDecryptionKeyId(document)
2143
+ attachments: document2.attachments ? structuredClone(document2.attachments) : [],
2144
+ updatedAt: document2.updatedAt,
2145
+ ...mockDecryptionKeyId(document2)
1903
2146
  },
1904
2147
  heads: mergedHeads,
1905
2148
  changesSince
@@ -1907,8 +2150,8 @@ function createDatabaseHandle(definition, peers) {
1907
2150
  },
1908
2151
  async applyAutomergeChangesBatch(items) {
1909
2152
  const results = [];
1910
- for (const item of items) {
1911
- results.push(await this.applyAutomergeChanges(item.docId, item.patch));
2153
+ for (const item2 of items) {
2154
+ results.push(await this.applyAutomergeChanges(item2.docId, item2.patch));
1912
2155
  }
1913
2156
  return results;
1914
2157
  },
@@ -2295,11 +2538,11 @@ function createDatabaseHandle(definition, peers) {
2295
2538
  };
2296
2539
  return {
2297
2540
  handle: databaseHandle,
2298
- 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) => ({
2299
2542
  origin: definition.info.id,
2300
- docId: document.id,
2301
- data: structuredClone(document.data),
2302
- createdAt: document.updatedAt ?? null
2543
+ docId: document2.id,
2544
+ data: structuredClone(document2.data),
2545
+ createdAt: document2.updatedAt ?? null
2303
2546
  })),
2304
2547
  documents: storedDocuments,
2305
2548
  notifyLiveQueries: refreshLiveQueries
@@ -2741,31 +2984,35 @@ function createFakeBridgeHost(options = {}) {
2741
2984
  );
2742
2985
  let previousWindowValue = typeof window === "undefined" ? void 0 : window;
2743
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
+ }
2744
3013
  const hostWindow = {
2745
3014
  postMessage(message, _targetOrigin, transfer) {
2746
- const payload = message;
2747
- if (!payload || payload.protocol !== PROTOCOL || payload.type !== "mindoodb-app:connect") {
2748
- return;
2749
- }
2750
- if (payload.launchId !== state.getLaunchContext().launchId) {
2751
- return;
2752
- }
2753
- const port = transfer?.[0];
2754
- if (!port) {
2755
- throw new Error(
2756
- "Expected the bridge connection to transfer a MessagePort."
2757
- );
2758
- }
2759
- connectedPorts.add(port);
2760
- port.addEventListener("message", (event) => {
2761
- void handlePortMessage(port, event.data);
2762
- });
2763
- port.start();
2764
- const connected = {
2765
- protocol: PROTOCOL,
2766
- type: "mindoodb-app:connected"
2767
- };
2768
- port.postMessage(connected);
3015
+ acceptConnection(message, transfer);
2769
3016
  }
2770
3017
  };
2771
3018
  function getWindowSearch() {
@@ -2885,6 +3132,8 @@ function createFakeBridgeHost(options = {}) {
2885
3132
  return state.getLicensedProducts();
2886
3133
  case "session.listDatabases":
2887
3134
  return state.listDatabaseInfos();
3135
+ case "session.listDocumentsSinceViewCursor":
3136
+ return { items: [], nextCursor: null };
2888
3137
  case "session.openDatabase":
2889
3138
  state.getDatabase(String(params.databaseId));
2890
3139
  return { ok: true };
@@ -3049,6 +3298,18 @@ function createFakeBridgeHost(options = {}) {
3049
3298
  String(params.docId),
3050
3299
  params.patch
3051
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();
3052
3313
  case "documents.create":
3053
3314
  return await state.getDatabase(String(params.databaseId)).documents.create(
3054
3315
  params.input
@@ -3412,6 +3673,7 @@ function createFakeBridgeHost(options = {}) {
3412
3673
  }
3413
3674
  if (message.kind === "request") {
3414
3675
  requests.push(message);
3676
+ options.onRequest?.(message);
3415
3677
  try {
3416
3678
  const customHandler = customRequestHandlers.get(message.method);
3417
3679
  if (customHandler) {
@@ -3426,7 +3688,7 @@ function createFakeBridgeHost(options = {}) {
3426
3688
  const builtinResult = await handleBuiltinRequest(message);
3427
3689
  if (builtinResult === void 0 && message.method !== "viewNavigators.dispose") {
3428
3690
  postRpcError(port, message.id, {
3429
- code: "unsupported-method",
3691
+ code: "method-not-found",
3430
3692
  message: `No fake bridge handler is configured for ${message.method}.`
3431
3693
  });
3432
3694
  return;
@@ -3474,8 +3736,13 @@ function createFakeBridgeHost(options = {}) {
3474
3736
  return state.proposedApps;
3475
3737
  },
3476
3738
  install,
3739
+ acceptConnection,
3477
3740
  dispose() {
3478
3741
  restoreWindow();
3742
+ connectedPorts.forEach((port) => {
3743
+ port.close();
3744
+ });
3745
+ connectedPorts.clear();
3479
3746
  viewSessions.clear();
3480
3747
  readStreams.clear();
3481
3748
  writeStreams.clear();
@@ -3586,9 +3853,13 @@ function createFakeBridgeHost(options = {}) {
3586
3853
  return controller;
3587
3854
  }
3588
3855
  export {
3856
+ capabilitiesForDefinitionPermissions,
3857
+ createBrowserTestHost,
3589
3858
  createEvaluatingViewNavigator,
3590
3859
  createFakeBridgeHost,
3591
3860
  createMockMindooDBAppBridge,
3592
3861
  createMockMindooDBAppSession,
3593
- loadMindoodbForTesting
3862
+ loadMindoodbForTesting,
3863
+ mockDatabasesFromDefinition,
3864
+ mountHavenTestHost
3594
3865
  };