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.
- package/README.md +185 -1
- package/TESTING.md +58 -0
- package/dist/{chunk-TPYDERAK.js → chunk-6NTVTL6G.js} +151 -3
- package/dist/index.d.ts +138 -3
- package/dist/index.js +414 -18
- package/dist/testing/index.d.ts +107 -2
- package/dist/testing/index.js +620 -135
- package/dist/{types-C-eyC839.d.ts → types-CsKw_niD.d.ts} +250 -5
- package/package.json +3 -3
package/dist/testing/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MINDOODB_APP_VALUE_TAG,
|
|
2
3
|
releaseMindooDBAppBridgeSessions
|
|
3
|
-
} from "../chunk-
|
|
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,
|
|
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 } =
|
|
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,
|
|
50
|
-
const { data: doc, origin, docId, createdAt, lastModifiedAt, witnessed, awaitingWitness } =
|
|
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
|
|
175
|
-
if (!matchesFilter(mindoodb, definition,
|
|
175
|
+
for (const document2 of documents) {
|
|
176
|
+
if (!matchesFilter(mindoodb, definition, document2)) {
|
|
176
177
|
continue;
|
|
177
178
|
}
|
|
178
|
-
const change = changes.get(
|
|
179
|
-
change.addEntry(
|
|
180
|
-
changes.set(
|
|
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 ?
|
|
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,
|
|
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, ...
|
|
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(
|
|
820
|
-
if (!
|
|
1143
|
+
function mockDecryptionKeyId(document2) {
|
|
1144
|
+
if (!document2.decryptionKeyId || document2.data._encryptFor) {
|
|
821
1145
|
return {};
|
|
822
1146
|
}
|
|
823
|
-
return { decryptionKeyId:
|
|
1147
|
+
return { decryptionKeyId: document2.decryptionKeyId };
|
|
824
1148
|
}
|
|
825
|
-
function loadMockAutomergeDocument(
|
|
826
|
-
if (
|
|
827
|
-
return Automerge.load(
|
|
1149
|
+
function loadMockAutomergeDocument(document2) {
|
|
1150
|
+
if (document2.automergeBinary) {
|
|
1151
|
+
return Automerge.load(document2.automergeBinary);
|
|
828
1152
|
}
|
|
829
1153
|
const initialDoc = Automerge.from({
|
|
830
|
-
...structuredClone(
|
|
831
|
-
body: typeof
|
|
1154
|
+
...structuredClone(document2.data),
|
|
1155
|
+
body: typeof document2.data.body === "string" ? document2.data.body : ""
|
|
832
1156
|
});
|
|
833
|
-
|
|
1157
|
+
document2.automergeBinary = Automerge.save(initialDoc);
|
|
834
1158
|
return initialDoc;
|
|
835
1159
|
}
|
|
836
|
-
function persistMockAutomergeDocument(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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(
|
|
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(
|
|
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
|
|
902
|
-
const text = collectMockText(
|
|
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(
|
|
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(
|
|
1241
|
+
const haystack = clause.fields ? clause.fields.map((field) => collectMockText(getFieldValue2(document2.data, field))).join(" ") : collectMockText(
|
|
918
1242
|
Object.fromEntries(
|
|
919
|
-
Object.entries(
|
|
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
|
|
944
|
-
if (
|
|
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(
|
|
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:
|
|
1279
|
+
doc: document2.data,
|
|
956
1280
|
values: {},
|
|
957
1281
|
origin: databaseId,
|
|
958
|
-
docId:
|
|
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(
|
|
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:
|
|
993
|
-
fields: projectMockFields(
|
|
994
|
-
lastModified: mockLastModified(
|
|
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:
|
|
1003
|
-
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(
|
|
1018
|
-
return
|
|
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
|
-
(
|
|
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
|
|
1212
|
-
if (
|
|
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" ?
|
|
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:
|
|
1545
|
+
doc: document2.data,
|
|
1222
1546
|
values: {},
|
|
1223
1547
|
origin: childDatabaseId,
|
|
1224
|
-
docId:
|
|
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:
|
|
1232
|
-
data:
|
|
1555
|
+
docId: document2.id,
|
|
1556
|
+
data: document2.data,
|
|
1233
1557
|
row: {
|
|
1234
|
-
docId:
|
|
1235
|
-
fields: projectMockFields(
|
|
1236
|
-
lastModified: mockLastModified(
|
|
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(
|
|
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((
|
|
1669
|
-
(
|
|
1670
|
-
).filter((
|
|
1671
|
-
(
|
|
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((
|
|
1997
|
+
const page = items.slice(offset, offset + limit).map((document2) => {
|
|
1674
1998
|
if (metadataOnly) {
|
|
1675
1999
|
return {
|
|
1676
|
-
id:
|
|
1677
|
-
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(
|
|
2007
|
+
getFieldValue2(document2.data, field)
|
|
1684
2008
|
])
|
|
1685
|
-
) :
|
|
2009
|
+
) : document2.data;
|
|
1686
2010
|
return {
|
|
1687
|
-
id:
|
|
2011
|
+
id: document2.id,
|
|
1688
2012
|
data: projectedData,
|
|
1689
|
-
attachmentCount:
|
|
1690
|
-
updatedAt:
|
|
1691
|
-
isDeleted: status !== "existing" ?
|
|
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((
|
|
1708
|
-
id:
|
|
1709
|
-
createdAt:
|
|
1710
|
-
decryptionKeyId:
|
|
1711
|
-
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
|
|
1717
|
-
if (!
|
|
2040
|
+
const document2 = storedDocuments.get(_docId);
|
|
2041
|
+
if (!document2 || document2.isDeleted || document2.inaccessible) {
|
|
1718
2042
|
return null;
|
|
1719
2043
|
}
|
|
1720
2044
|
return {
|
|
1721
|
-
id:
|
|
1722
|
-
data: structuredClone(
|
|
1723
|
-
heads:
|
|
1724
|
-
attachments:
|
|
1725
|
-
updatedAt:
|
|
1726
|
-
...mockDecryptionKeyId(
|
|
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
|
|
1731
|
-
if (!
|
|
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:
|
|
1737
|
-
spans: structuredClone(readRichTextSpansAtPath(
|
|
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
|
|
1742
|
-
if (!
|
|
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(
|
|
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
|
|
1753
|
-
if (!
|
|
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(
|
|
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(
|
|
1762
|
-
|
|
1763
|
-
const mergedHeads =
|
|
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:
|
|
1778
|
-
data: structuredClone(
|
|
2140
|
+
id: document2.id,
|
|
2141
|
+
data: structuredClone(document2.data),
|
|
1779
2142
|
heads: mergedHeads,
|
|
1780
|
-
attachments:
|
|
1781
|
-
updatedAt:
|
|
1782
|
-
...mockDecryptionKeyId(
|
|
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((
|
|
2541
|
+
listViewDocuments: () => Array.from(storedDocuments.values()).filter((document2) => !document2.isDeleted && !document2.inaccessible).map((document2) => ({
|
|
2165
2542
|
origin: definition.info.id,
|
|
2166
|
-
docId:
|
|
2167
|
-
data: structuredClone(
|
|
2168
|
-
createdAt:
|
|
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
|
-
|
|
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: "
|
|
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
|
};
|