mindoodb-app-sdk 0.0.41 → 0.0.43

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.
@@ -0,0 +1,1426 @@
1
+ // src/hostShortcuts.ts
2
+ var DEFAULT_HAVEN_HOST_SHORTCUTS = [
3
+ { action: "open-workspace", code: "Enter", shiftKey: true, metaOrCtrl: true },
4
+ { action: "toggle-app-drawer", code: "Space", shiftKey: true, metaOrCtrl: true }
5
+ ];
6
+ var HOST_SHORTCUT_ACTIONS = /* @__PURE__ */ new Set([
7
+ "open-workspace",
8
+ "toggle-app-drawer"
9
+ ]);
10
+ function normalizeShortcutCode(code) {
11
+ return code === "NumpadEnter" ? "Enter" : code;
12
+ }
13
+ function matchHostShortcut(event, shortcuts = DEFAULT_HAVEN_HOST_SHORTCUTS) {
14
+ if (event.repeat || event.altKey) {
15
+ return null;
16
+ }
17
+ const primary = event.metaKey || event.ctrlKey;
18
+ const code = normalizeShortcutCode(event.code);
19
+ for (const binding of shortcuts) {
20
+ if (code !== binding.code) {
21
+ continue;
22
+ }
23
+ if (Boolean(event.shiftKey) !== Boolean(binding.shiftKey)) {
24
+ continue;
25
+ }
26
+ if (Boolean(binding.metaOrCtrl) !== primary) {
27
+ continue;
28
+ }
29
+ return binding.action;
30
+ }
31
+ return null;
32
+ }
33
+ function isHostShortcutAction(value) {
34
+ return typeof value === "string" && HOST_SHORTCUT_ACTIONS.has(value);
35
+ }
36
+ function sanitizeHostShortcutBindings(value) {
37
+ if (!Array.isArray(value)) {
38
+ return null;
39
+ }
40
+ const bindings = [];
41
+ for (const entry of value) {
42
+ if (!entry || typeof entry !== "object") {
43
+ return null;
44
+ }
45
+ const candidate = entry;
46
+ if (!isHostShortcutAction(candidate.action) || typeof candidate.code !== "string" || !candidate.code) {
47
+ return null;
48
+ }
49
+ bindings.push({
50
+ action: candidate.action,
51
+ code: candidate.code,
52
+ shiftKey: Boolean(candidate.shiftKey),
53
+ metaOrCtrl: Boolean(candidate.metaOrCtrl)
54
+ });
55
+ }
56
+ return bindings;
57
+ }
58
+ function applyHostedDocumentOverscrollContain(doc = globalThis.document) {
59
+ if (!doc?.documentElement) {
60
+ return;
61
+ }
62
+ doc.documentElement.style.overscrollBehavior = "contain";
63
+ if (doc.body) {
64
+ doc.body.style.overscrollBehavior = "contain";
65
+ }
66
+ }
67
+ function installHostShortcutCapture(options) {
68
+ const target = options.target ?? (typeof document === "undefined" ? void 0 : document);
69
+ if (!target) {
70
+ return () => void 0;
71
+ }
72
+ const onKeyDown = (event) => {
73
+ const action = matchHostShortcut(event, options.getShortcuts());
74
+ if (!action) {
75
+ return;
76
+ }
77
+ event.preventDefault();
78
+ event.stopPropagation();
79
+ options.onInvoke(action);
80
+ };
81
+ target.addEventListener("keydown", onKeyDown, true);
82
+ return () => {
83
+ target.removeEventListener("keydown", onKeyDown, true);
84
+ };
85
+ }
86
+
87
+ // src/client/createMindooDBAppBridge.ts
88
+ import { parseMindooDBFormulaBooleanExpression } from "mindoodb-view-language";
89
+
90
+ // src/client/portRpcClient.ts
91
+ var PROTOCOL = "mindoodb-app-bridge";
92
+ function normalizeRpcValue(value, seen = /* @__PURE__ */ new WeakMap()) {
93
+ if (typeof value === "function") {
94
+ throw new Error("Bridge RPC params cannot include functions.");
95
+ }
96
+ if (typeof value === "symbol") {
97
+ throw new Error("Bridge RPC params cannot include symbols.");
98
+ }
99
+ if (value === null || typeof value !== "object") {
100
+ return value;
101
+ }
102
+ if (value instanceof Date) {
103
+ return new Date(value.getTime());
104
+ }
105
+ if (value instanceof ArrayBuffer) {
106
+ return value.slice(0);
107
+ }
108
+ if (ArrayBuffer.isView(value)) {
109
+ return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
110
+ }
111
+ if (seen.has(value)) {
112
+ return seen.get(value);
113
+ }
114
+ if (Array.isArray(value)) {
115
+ const copy2 = [];
116
+ seen.set(value, copy2);
117
+ for (const entry of value) {
118
+ copy2.push(normalizeRpcValue(entry, seen));
119
+ }
120
+ return copy2;
121
+ }
122
+ const copy = {};
123
+ seen.set(value, copy);
124
+ for (const [key, entry] of Object.entries(value)) {
125
+ copy[key] = normalizeRpcValue(entry, seen);
126
+ }
127
+ return copy;
128
+ }
129
+ function toBridgeError(error) {
130
+ const wrapped = new Error(error.message);
131
+ wrapped.name = error.code || "MindooDBAppBridgeError";
132
+ return wrapped;
133
+ }
134
+ function isRpcMessage(message) {
135
+ if (!isProtocolMessage(message)) {
136
+ return false;
137
+ }
138
+ return "id" in message && typeof message.id === "string";
139
+ }
140
+ function isProtocolMessage(message) {
141
+ return Boolean(
142
+ message && typeof message === "object" && "protocol" in message && message.protocol === PROTOCOL
143
+ );
144
+ }
145
+ var PortRpcClient = class {
146
+ constructor(port) {
147
+ this.port = port;
148
+ this.handleMessage = this.handleMessage.bind(this);
149
+ this.port.addEventListener("message", this.handleMessage);
150
+ this.port.start();
151
+ }
152
+ port;
153
+ pending = /* @__PURE__ */ new Map();
154
+ listeners = /* @__PURE__ */ new Set();
155
+ requestCounter = 0;
156
+ disposed = false;
157
+ /** Sends one RPC request and resolves with the typed response payload. */
158
+ async call(method, params) {
159
+ if (this.disposed) {
160
+ throw new Error("The bridge connection has already been closed.");
161
+ }
162
+ const normalizedParams = normalizeRpcValue(params);
163
+ const id = `${Date.now()}-${this.requestCounter += 1}`;
164
+ const message = {
165
+ protocol: PROTOCOL,
166
+ kind: "request",
167
+ id,
168
+ method,
169
+ params: normalizedParams
170
+ };
171
+ return await new Promise((resolve, reject) => {
172
+ this.pending.set(id, { resolve, reject });
173
+ try {
174
+ this.port.postMessage(message);
175
+ } catch (error) {
176
+ this.pending.delete(id);
177
+ console.error("MindooDB app bridge RPC postMessage failed.", { method, params: normalizedParams, error });
178
+ reject(error instanceof Error ? error : new Error(String(error)));
179
+ }
180
+ });
181
+ }
182
+ /** Closes the port and rejects any requests that are still waiting for a response. */
183
+ dispose() {
184
+ if (this.disposed) {
185
+ return;
186
+ }
187
+ this.disposed = true;
188
+ this.port.removeEventListener("message", this.handleMessage);
189
+ this.port.close();
190
+ this.pending.forEach(({ reject }) => reject(new Error("The bridge connection was closed.")));
191
+ this.pending.clear();
192
+ this.listeners.clear();
193
+ }
194
+ /** Subscribes to all protocol messages, including attachment stream traffic. */
195
+ addMessageListener(listener) {
196
+ this.listeners.add(listener);
197
+ return () => {
198
+ this.listeners.delete(listener);
199
+ };
200
+ }
201
+ /** Posts a raw protocol message over the port, optionally with transferables. */
202
+ postMessage(message, transfer = []) {
203
+ if (this.disposed) {
204
+ throw new Error("The bridge connection has already been closed.");
205
+ }
206
+ try {
207
+ this.port.postMessage(message, transfer);
208
+ } catch (error) {
209
+ console.error("MindooDB app bridge raw postMessage failed.", { message, transferCount: transfer.length, error });
210
+ throw error;
211
+ }
212
+ }
213
+ /** Dispatches incoming messages to listeners and resolves matching RPC requests. */
214
+ handleMessage(event) {
215
+ if (!isProtocolMessage(event.data)) {
216
+ return;
217
+ }
218
+ const message = event.data;
219
+ this.listeners.forEach((listener) => listener(message));
220
+ if (!isRpcMessage(message)) {
221
+ return;
222
+ }
223
+ if (message.kind !== "success" && message.kind !== "error") {
224
+ return;
225
+ }
226
+ const pending = this.pending.get(message.id);
227
+ if (!pending) {
228
+ return;
229
+ }
230
+ this.pending.delete(message.id);
231
+ if (message.kind === "success") {
232
+ pending.resolve(message.result);
233
+ return;
234
+ }
235
+ pending.reject(toBridgeError(message.error));
236
+ }
237
+ };
238
+
239
+ // src/client/createMindooDBAppBridge.ts
240
+ var PROTOCOL2 = "mindoodb-app-bridge";
241
+ var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
242
+ function isMethodNotFoundError(error) {
243
+ return error instanceof Error && error.name === "method-not-found";
244
+ }
245
+ function toTransferableArrayBuffer(chunk) {
246
+ return chunk.buffer.slice(
247
+ chunk.byteOffset,
248
+ chunk.byteOffset + chunk.byteLength
249
+ );
250
+ }
251
+ function isStreamChunkFor(streamId, message) {
252
+ return message.kind === "stream-chunk" && message.streamId === streamId;
253
+ }
254
+ function isStreamAckFor(streamId, message) {
255
+ return message.kind === "stream-ack" && message.streamId === streamId;
256
+ }
257
+ function isStreamErrorFor(streamId, message) {
258
+ return message.kind === "stream-error" && message.streamId === streamId;
259
+ }
260
+ function isQueryResultFor(subscriptionId, message) {
261
+ return message.kind === "query-result" && message.subscriptionId === subscriptionId;
262
+ }
263
+ function isViewChangedFor(navigatorId, message) {
264
+ return message.kind === "view-changed" && message.navigatorId === navigatorId;
265
+ }
266
+ function normalizeDocumentQuery(query) {
267
+ if (!query) {
268
+ return {};
269
+ }
270
+ const { filter, ...rest } = query;
271
+ return {
272
+ ...rest,
273
+ ...filter !== void 0 ? {
274
+ filter: typeof filter === "string" ? parseMindooDBFormulaBooleanExpression(filter) : filter
275
+ } : {}
276
+ };
277
+ }
278
+ function isThemeChangedMessage(message) {
279
+ return message.kind === "theme-changed";
280
+ }
281
+ function isViewportChangedMessage(message) {
282
+ return message.kind === "viewport-changed";
283
+ }
284
+ function isUiPreferencesChangedMessage(message) {
285
+ return message.kind === "ui-preferences-changed";
286
+ }
287
+ function isLocaleChangedMessage(message) {
288
+ return message.kind === "locale-changed";
289
+ }
290
+ function toStreamError(message) {
291
+ const error = new Error(message.error.message);
292
+ error.name = message.error.code || "MindooDBAppBridgeStreamError";
293
+ return error;
294
+ }
295
+ function resolveLaunchId(options) {
296
+ if (options?.launchId) {
297
+ return options.launchId;
298
+ }
299
+ if (typeof window === "undefined") {
300
+ throw new Error(
301
+ "A launchId must be provided when connecting outside the browser."
302
+ );
303
+ }
304
+ const search = new URLSearchParams(window.location.search);
305
+ const launchId = search.get("mindoodbAppLaunchId");
306
+ if (!launchId) {
307
+ throw new Error("Missing mindoodbAppLaunchId in the current URL.");
308
+ }
309
+ return launchId;
310
+ }
311
+ function shouldUseWindowedHostTab() {
312
+ if (typeof window === "undefined") {
313
+ return false;
314
+ }
315
+ return window.parent === window && Boolean(window.opener);
316
+ }
317
+ function resolveTargetWindow() {
318
+ if (typeof window === "undefined") {
319
+ throw new Error(
320
+ "The MindooDB app bridge is only available in the browser."
321
+ );
322
+ }
323
+ if (window.parent && window.parent !== window) {
324
+ return window.parent;
325
+ }
326
+ if (window.opener) {
327
+ return window.opener;
328
+ }
329
+ throw new Error("Could not find a MindooDB Haven host window.");
330
+ }
331
+ function waitForConnectedPort(options, launchId) {
332
+ const targetWindow = resolveTargetWindow();
333
+ const targetOrigin = options?.targetOrigin ?? "*";
334
+ const channel = new MessageChannel();
335
+ const timeoutMs = options?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
336
+ return new Promise((resolve, reject) => {
337
+ let timeoutId = null;
338
+ const cleanup = () => {
339
+ if (timeoutId !== null) {
340
+ window.clearTimeout(timeoutId);
341
+ }
342
+ channel.port1.removeEventListener(
343
+ "message",
344
+ handleMessage
345
+ );
346
+ };
347
+ const handleMessage = (event) => {
348
+ const payload = event.data;
349
+ if (!payload || payload.protocol !== PROTOCOL2) {
350
+ return;
351
+ }
352
+ if (payload.type === "mindoodb-app:connected") {
353
+ cleanup();
354
+ resolve(channel.port1);
355
+ return;
356
+ }
357
+ if (payload.type === "mindoodb-app:error") {
358
+ cleanup();
359
+ channel.port1.close();
360
+ const message2 = typeof payload.error === "string" && payload.error.trim() ? payload.error : "MindooDB Haven bridge connection failed.";
361
+ reject(new Error(message2));
362
+ }
363
+ };
364
+ timeoutId = window.setTimeout(() => {
365
+ cleanup();
366
+ channel.port1.close();
367
+ reject(
368
+ new Error(
369
+ `Timed out while connecting to the MindooDB Haven bridge for launch ${launchId}.`
370
+ )
371
+ );
372
+ }, timeoutMs);
373
+ channel.port1.addEventListener("message", handleMessage);
374
+ channel.port1.start();
375
+ const message = {
376
+ protocol: PROTOCOL2,
377
+ type: "mindoodb-app:connect",
378
+ launchId
379
+ };
380
+ targetWindow.postMessage(message, targetOrigin, [channel.port2]);
381
+ });
382
+ }
383
+ var MindooDBAppViewNavigatorImpl = class {
384
+ constructor(rpc, navigatorId, viewCursor) {
385
+ this.rpc = rpc;
386
+ this.navigatorId = navigatorId;
387
+ this.viewCursor = viewCursor;
388
+ }
389
+ rpc;
390
+ navigatorId;
391
+ viewCursor;
392
+ /** Shorthand: call an RPC method with the navigator ID automatically injected. */
393
+ async call(method, params = {}) {
394
+ return await this.rpc.call(method, {
395
+ navigatorId: this.navigatorId,
396
+ ...params
397
+ });
398
+ }
399
+ async getDefinition() {
400
+ return await this.call("viewNavigators.getDefinition");
401
+ }
402
+ async getViewCursor() {
403
+ return this.viewCursor;
404
+ }
405
+ async refresh() {
406
+ const result = await this.call(
407
+ "viewNavigators.refresh"
408
+ );
409
+ this.viewCursor = result.viewCursor;
410
+ return this.viewCursor;
411
+ }
412
+ onDidUpdate(listener) {
413
+ return this.rpc.addMessageListener((message) => {
414
+ if (isViewChangedFor(this.navigatorId, message)) {
415
+ listener(message.stats);
416
+ }
417
+ });
418
+ }
419
+ async getCurrentEntry() {
420
+ return await this.call("viewNavigators.current.get");
421
+ }
422
+ async gotoFirst() {
423
+ return await this.call("viewNavigators.goto.first");
424
+ }
425
+ async gotoLast() {
426
+ return await this.call("viewNavigators.goto.last");
427
+ }
428
+ async gotoNext() {
429
+ return await this.call("viewNavigators.goto.next");
430
+ }
431
+ async gotoPrev() {
432
+ return await this.call("viewNavigators.goto.prev");
433
+ }
434
+ async gotoNextSibling() {
435
+ return await this.call("viewNavigators.goto.nextSibling");
436
+ }
437
+ async gotoPrevSibling() {
438
+ return await this.call("viewNavigators.goto.prevSibling");
439
+ }
440
+ async gotoParent() {
441
+ return await this.call("viewNavigators.goto.parent");
442
+ }
443
+ async gotoFirstChild() {
444
+ return await this.call("viewNavigators.goto.firstChild");
445
+ }
446
+ async gotoLastChild() {
447
+ return await this.call("viewNavigators.goto.lastChild");
448
+ }
449
+ async gotoPos(position) {
450
+ return await this.call("viewNavigators.goto.pos", { position });
451
+ }
452
+ async getPos(position) {
453
+ return await this.call("viewNavigators.pos.get", { position });
454
+ }
455
+ async findCategoryEntryByParts(parts) {
456
+ return await this.call("viewNavigators.category.findByParts", { parts });
457
+ }
458
+ async entriesForward(options) {
459
+ return await this.call("viewNavigators.entries.forward", {
460
+ options: options ?? {}
461
+ });
462
+ }
463
+ async entriesBackward(options) {
464
+ return await this.call("viewNavigators.entries.backward", {
465
+ options: options ?? {}
466
+ });
467
+ }
468
+ async gotoNextSelected() {
469
+ return await this.call("viewNavigators.goto.nextSelected");
470
+ }
471
+ async gotoPrevSelected() {
472
+ return await this.call("viewNavigators.goto.prevSelected");
473
+ }
474
+ async select(origin, docId, selectParentCategories) {
475
+ await this.call("viewNavigators.selection.select", {
476
+ origin,
477
+ docId,
478
+ selectParentCategories
479
+ });
480
+ }
481
+ async deselect(origin, docId) {
482
+ await this.call("viewNavigators.selection.deselect", { origin, docId });
483
+ }
484
+ async selectAllEntries() {
485
+ await this.call("viewNavigators.selection.selectAll");
486
+ }
487
+ async deselectAllEntries() {
488
+ await this.call("viewNavigators.selection.deselectAll");
489
+ }
490
+ async isSelected(origin, docId) {
491
+ return await this.call("viewNavigators.selection.isSelected", {
492
+ origin,
493
+ docId
494
+ });
495
+ }
496
+ async getSelectionState() {
497
+ return await this.call("viewNavigators.selection.get");
498
+ }
499
+ async setSelectionState(state) {
500
+ await this.call("viewNavigators.selection.set", { state });
501
+ }
502
+ async expand(origin, docId) {
503
+ await this.call("viewNavigators.expansion.expand", { origin, docId });
504
+ }
505
+ async collapse(origin, docId) {
506
+ await this.call("viewNavigators.expansion.collapse", { origin, docId });
507
+ }
508
+ async expandAll() {
509
+ await this.call("viewNavigators.expansion.expandAll");
510
+ }
511
+ async collapseAll() {
512
+ await this.call("viewNavigators.expansion.collapseAll");
513
+ }
514
+ async expandToLevel(level) {
515
+ await this.call("viewNavigators.expansion.expandToLevel", { level });
516
+ }
517
+ async isExpanded(entryKey) {
518
+ return await this.call("viewNavigators.expansion.isExpanded", { entryKey });
519
+ }
520
+ async getExpansionState() {
521
+ return await this.call("viewNavigators.expansion.get");
522
+ }
523
+ async setExpansionState(state) {
524
+ await this.call("viewNavigators.expansion.set", { state });
525
+ }
526
+ async childEntries(entryKey, descending) {
527
+ return await this.call("viewNavigators.children.entries", {
528
+ entryKey,
529
+ descending
530
+ });
531
+ }
532
+ async childCategories(entryKey, descending) {
533
+ return await this.call("viewNavigators.children.categories", {
534
+ entryKey,
535
+ descending
536
+ });
537
+ }
538
+ async childDocuments(entryKey, descending) {
539
+ return await this.call("viewNavigators.children.documents", {
540
+ entryKey,
541
+ descending
542
+ });
543
+ }
544
+ async childCategoriesByKey(entryKey, key, exact, descending) {
545
+ return await this.call("viewNavigators.children.categoriesByKey", {
546
+ entryKey,
547
+ key,
548
+ exact,
549
+ descending
550
+ });
551
+ }
552
+ async childDocumentsByKey(entryKey, key, exact, descending) {
553
+ return await this.call("viewNavigators.children.documentsByKey", {
554
+ entryKey,
555
+ key,
556
+ exact,
557
+ descending
558
+ });
559
+ }
560
+ async childCategoriesBetween(entryKey, range) {
561
+ return await this.call("viewNavigators.children.categoriesBetween", {
562
+ entryKey,
563
+ range
564
+ });
565
+ }
566
+ async childDocumentsBetween(entryKey, range) {
567
+ return await this.call("viewNavigators.children.documentsBetween", {
568
+ entryKey,
569
+ range
570
+ });
571
+ }
572
+ async getSortedDocIds(descending) {
573
+ return await this.call(
574
+ "viewNavigators.sortedDocIds.get",
575
+ { descending }
576
+ );
577
+ }
578
+ async getSortedDocIdsScoped(entryKey, descending) {
579
+ return await this.call(
580
+ "viewNavigators.sortedDocIds.scoped",
581
+ { entryKey, descending }
582
+ );
583
+ }
584
+ async dispose() {
585
+ await this.call("viewNavigators.dispose");
586
+ }
587
+ };
588
+ var MindooDBAppReadableAttachmentStreamImpl = class {
589
+ constructor(rpc, streamId) {
590
+ this.rpc = rpc;
591
+ this.streamId = streamId;
592
+ this.unsubscribe = this.rpc.addMessageListener((message) => {
593
+ if (isStreamChunkFor(this.streamId, message)) {
594
+ const pending = this.pendingRead;
595
+ this.pendingRead = null;
596
+ if (!pending) {
597
+ return;
598
+ }
599
+ pending.resolve(
600
+ message.done ? null : new Uint8Array(message.chunk ?? new ArrayBuffer(0))
601
+ );
602
+ if (message.done) {
603
+ this.closed = true;
604
+ this.unsubscribe();
605
+ }
606
+ return;
607
+ }
608
+ if (isStreamErrorFor(this.streamId, message)) {
609
+ const pending = this.pendingRead;
610
+ this.pendingRead = null;
611
+ pending?.reject(toStreamError(message));
612
+ this.closed = true;
613
+ this.unsubscribe();
614
+ }
615
+ });
616
+ }
617
+ rpc;
618
+ streamId;
619
+ unsubscribe;
620
+ closed = false;
621
+ pendingRead = null;
622
+ /** Requests and returns the next attachment chunk, or `null` when the stream ends. */
623
+ async read() {
624
+ if (this.closed) {
625
+ return null;
626
+ }
627
+ if (this.pendingRead) {
628
+ throw new Error(
629
+ "A read() call is already in progress for this attachment stream."
630
+ );
631
+ }
632
+ return await new Promise((resolve, reject) => {
633
+ this.pendingRead = { resolve, reject };
634
+ this.rpc.postMessage({
635
+ protocol: PROTOCOL2,
636
+ kind: "stream-read",
637
+ streamId: this.streamId
638
+ });
639
+ });
640
+ }
641
+ /** Closes the read stream and releases any pending read waiter. */
642
+ async close() {
643
+ if (this.closed) {
644
+ return;
645
+ }
646
+ this.closed = true;
647
+ this.pendingRead?.resolve(null);
648
+ this.pendingRead = null;
649
+ this.rpc.postMessage({
650
+ protocol: PROTOCOL2,
651
+ kind: "stream-close",
652
+ streamId: this.streamId
653
+ });
654
+ this.unsubscribe();
655
+ }
656
+ };
657
+ var MindooDBAppWritableAttachmentStreamImpl = class {
658
+ constructor(rpc, streamId) {
659
+ this.rpc = rpc;
660
+ this.streamId = streamId;
661
+ this.unsubscribe = this.rpc.addMessageListener((message) => {
662
+ if (isStreamAckFor(this.streamId, message)) {
663
+ const inflight = this.inflight;
664
+ this.inflight = null;
665
+ inflight?.resolve();
666
+ return;
667
+ }
668
+ if (isStreamErrorFor(this.streamId, message)) {
669
+ const inflight = this.inflight;
670
+ this.inflight = null;
671
+ inflight?.reject(toStreamError(message));
672
+ this.closed = true;
673
+ this.unsubscribe();
674
+ }
675
+ });
676
+ }
677
+ rpc;
678
+ streamId;
679
+ unsubscribe;
680
+ closed = false;
681
+ inflight = null;
682
+ queue = Promise.resolve();
683
+ /** Ensures write/close/abort operations run strictly in order. */
684
+ enqueue(operation) {
685
+ const next = this.queue.then(operation);
686
+ this.queue = next.catch(() => void 0);
687
+ return next;
688
+ }
689
+ /** Sends one stream operation and waits for its ack/error response. */
690
+ async waitForAck(send) {
691
+ return await new Promise((resolve, reject) => {
692
+ this.inflight = { resolve, reject };
693
+ send();
694
+ });
695
+ }
696
+ /** Writes one binary chunk to the host-managed attachment stream. */
697
+ async write(chunk) {
698
+ if (this.closed) {
699
+ throw new Error("The attachment write stream is already closed.");
700
+ }
701
+ return await this.enqueue(async () => {
702
+ const buffer = toTransferableArrayBuffer(chunk);
703
+ await this.waitForAck(() => {
704
+ this.rpc.postMessage(
705
+ {
706
+ protocol: PROTOCOL2,
707
+ kind: "stream-write",
708
+ streamId: this.streamId,
709
+ chunk: buffer
710
+ },
711
+ [buffer]
712
+ );
713
+ });
714
+ });
715
+ }
716
+ /** Gracefully closes the write stream after any queued writes complete. */
717
+ async close() {
718
+ if (this.closed) {
719
+ return;
720
+ }
721
+ this.closed = true;
722
+ await this.enqueue(async () => {
723
+ await this.waitForAck(() => {
724
+ this.rpc.postMessage({
725
+ protocol: PROTOCOL2,
726
+ kind: "stream-close",
727
+ streamId: this.streamId
728
+ });
729
+ });
730
+ });
731
+ this.unsubscribe();
732
+ }
733
+ /** Aborts the write stream after any in-flight work has settled. */
734
+ async abort() {
735
+ if (this.closed) {
736
+ return;
737
+ }
738
+ this.closed = true;
739
+ await this.enqueue(async () => {
740
+ await this.waitForAck(() => {
741
+ this.rpc.postMessage({
742
+ protocol: PROTOCOL2,
743
+ kind: "stream-abort",
744
+ streamId: this.streamId
745
+ });
746
+ });
747
+ });
748
+ this.unsubscribe();
749
+ }
750
+ };
751
+ var MindooDBAppDatabaseImpl = class {
752
+ constructor(rpc, databaseId) {
753
+ this.rpc = rpc;
754
+ this.databaseId = databaseId;
755
+ this.documents = {
756
+ list: async (query) => await this.rpc.call("documents.list", {
757
+ databaseId: this.databaseId,
758
+ query: query ?? {}
759
+ }),
760
+ listInaccessible: async (query) => await this.rpc.call("documents.listInaccessible", {
761
+ databaseId: this.databaseId,
762
+ query: query ?? {}
763
+ }),
764
+ query: async (query) => await this.rpc.call("documents.query", {
765
+ databaseId: this.databaseId,
766
+ query: normalizeDocumentQuery(query)
767
+ }),
768
+ liveQuery: async (query, onResult) => {
769
+ const { subscriptionId, result } = await this.rpc.call("documents.liveQuery.subscribe", {
770
+ databaseId: this.databaseId,
771
+ query: normalizeDocumentQuery(query)
772
+ });
773
+ let disposed = false;
774
+ const unsubscribe = this.rpc.addMessageListener((message) => {
775
+ if (!disposed && isQueryResultFor(subscriptionId, message)) {
776
+ onResult(message.result);
777
+ }
778
+ });
779
+ onResult(result);
780
+ const subscription = {
781
+ refresh: async () => {
782
+ if (disposed) {
783
+ return;
784
+ }
785
+ await this.rpc.call("documents.liveQuery.refresh", {
786
+ subscriptionId
787
+ });
788
+ },
789
+ dispose: async () => {
790
+ if (disposed) {
791
+ return;
792
+ }
793
+ disposed = true;
794
+ unsubscribe();
795
+ await this.rpc.call("documents.liveQuery.unsubscribe", {
796
+ subscriptionId
797
+ });
798
+ }
799
+ };
800
+ return subscription;
801
+ },
802
+ getHeadCursor: async () => await this.rpc.call("documents.getHeadCursor", {
803
+ databaseId: this.databaseId
804
+ }),
805
+ get: async (docId) => await this.rpc.call("documents.get", {
806
+ databaseId: this.databaseId,
807
+ docId
808
+ }),
809
+ getRichText: async (docId, path, options) => await this.rpc.call("documents.richText.get", {
810
+ databaseId: this.databaseId,
811
+ docId,
812
+ path,
813
+ revisionId: options?.revisionId
814
+ }),
815
+ getAutomergeSnapshot: async (docId, options) => await this.rpc.call("documents.automerge.getSnapshot", {
816
+ databaseId: this.databaseId,
817
+ docId,
818
+ revisionId: options?.revisionId
819
+ }),
820
+ applyAutomergeChanges: async (docId, patch) => await this.rpc.call("documents.automerge.applyChanges", {
821
+ databaseId: this.databaseId,
822
+ docId,
823
+ patch
824
+ }),
825
+ create: async (input) => await this.rpc.call("documents.create", {
826
+ databaseId: this.databaseId,
827
+ input
828
+ }),
829
+ createMany: async (inputs) => {
830
+ if (inputs.length === 0) {
831
+ return { ids: [] };
832
+ }
833
+ try {
834
+ return await this.rpc.call("documents.createMany", {
835
+ databaseId: this.databaseId,
836
+ inputs
837
+ });
838
+ } catch (error) {
839
+ if (!isMethodNotFoundError(error)) {
840
+ throw error;
841
+ }
842
+ const ids = [];
843
+ for (const input of inputs) {
844
+ const document2 = await this.rpc.call("documents.create", {
845
+ databaseId: this.databaseId,
846
+ input
847
+ });
848
+ ids.push(document2.id);
849
+ }
850
+ return { ids };
851
+ }
852
+ },
853
+ update: async (docId, patch) => await this.rpc.call("documents.update", {
854
+ databaseId: this.databaseId,
855
+ docId,
856
+ patch
857
+ }),
858
+ addRecipients: async (docId, recipients) => await this.rpc.call("documents.addRecipients", {
859
+ databaseId: this.databaseId,
860
+ docId,
861
+ recipients
862
+ }),
863
+ removeRecipients: async (docId, recipients) => await this.rpc.call("documents.removeRecipients", {
864
+ databaseId: this.databaseId,
865
+ docId,
866
+ recipients
867
+ }),
868
+ setRecipients: async (docId, recipients, options) => await this.rpc.call("documents.setRecipients", {
869
+ databaseId: this.databaseId,
870
+ docId,
871
+ recipients,
872
+ recipientOptions: options
873
+ }),
874
+ delete: async (docId) => await this.rpc.call("documents.delete", {
875
+ databaseId: this.databaseId,
876
+ docId
877
+ }),
878
+ deleteMany: async (docIds) => {
879
+ if (docIds.length === 0) {
880
+ return { ok: true };
881
+ }
882
+ try {
883
+ return await this.rpc.call("documents.deleteMany", {
884
+ databaseId: this.databaseId,
885
+ docIds
886
+ });
887
+ } catch (error) {
888
+ if (!isMethodNotFoundError(error)) {
889
+ throw error;
890
+ }
891
+ for (const docId of docIds) {
892
+ await this.rpc.call("documents.delete", {
893
+ databaseId: this.databaseId,
894
+ docId
895
+ });
896
+ }
897
+ return { ok: true };
898
+ }
899
+ },
900
+ undelete: async (docId) => await this.rpc.call("documents.undelete", {
901
+ databaseId: this.databaseId,
902
+ docId
903
+ }),
904
+ canCreate: async (input) => await this.rpc.call("documents.canCreate", {
905
+ databaseId: this.databaseId,
906
+ input: input ?? {}
907
+ }),
908
+ canChange: async (docId) => await this.rpc.call("documents.canChange", {
909
+ databaseId: this.databaseId,
910
+ docId
911
+ }),
912
+ canDelete: async (docId) => await this.rpc.call("documents.canDelete", {
913
+ databaseId: this.databaseId,
914
+ docId
915
+ }),
916
+ canUndelete: async (docId) => await this.rpc.call("documents.canUndelete", {
917
+ databaseId: this.databaseId,
918
+ docId
919
+ }),
920
+ getDefaultCreateKeyId: async () => await this.rpc.call("documents.getDefaultCreateKeyId", {
921
+ databaseId: this.databaseId
922
+ }),
923
+ listCreateKeys: async () => {
924
+ try {
925
+ return await this.rpc.call("documents.listCreateKeys", {
926
+ databaseId: this.databaseId
927
+ });
928
+ } catch (error) {
929
+ if (!isMethodNotFoundError(error)) {
930
+ throw error;
931
+ }
932
+ const keyId = await this.rpc.call("documents.getDefaultCreateKeyId", {
933
+ databaseId: this.databaseId
934
+ });
935
+ return [{ keyId, isDefault: true }];
936
+ }
937
+ },
938
+ listHistory: async (docId) => await this.rpc.call("documents.history.list", {
939
+ databaseId: this.databaseId,
940
+ docId
941
+ }),
942
+ getAtTimestamp: async (docId, timestamp) => await this.rpc.call("documents.history.getAtTimestamp", {
943
+ databaseId: this.databaseId,
944
+ docId,
945
+ timestamp
946
+ }),
947
+ getAtRevision: async (docId, revisionId, options) => await this.rpc.call("documents.history.getAtRevision", {
948
+ databaseId: this.databaseId,
949
+ docId,
950
+ revisionId,
951
+ phase: options?.phase
952
+ }),
953
+ getAtHeads: async (docId, headIds) => await this.rpc.call("documents.history.getAtHeads", {
954
+ databaseId: this.databaseId,
955
+ docId,
956
+ headIds
957
+ }),
958
+ listVerification: async (docId, revisionIds) => await this.rpc.call("documents.history.listVerification", {
959
+ databaseId: this.databaseId,
960
+ docId,
961
+ revisionIds
962
+ })
963
+ };
964
+ this.identity = {
965
+ signStatement: async (input) => await this.rpc.call("identity.signStatement", {
966
+ databaseId: this.databaseId,
967
+ ...input
968
+ })
969
+ };
970
+ this.timestamps = {
971
+ rfc3161: async (input) => await this.rpc.call("timestamps.rfc3161", {
972
+ databaseId: this.databaseId,
973
+ ...input
974
+ }),
975
+ listProviders: async () => await this.rpc.call("timestamps.listProviders", {
976
+ databaseId: this.databaseId
977
+ })
978
+ };
979
+ this.directory = {
980
+ excerpt: async (publicKeys) => await this.rpc.call("directory.excerpt", {
981
+ databaseId: this.databaseId,
982
+ publicKeys
983
+ }),
984
+ listUsers: async () => {
985
+ try {
986
+ return await this.rpc.call("directory.listUsers", {
987
+ databaseId: this.databaseId
988
+ });
989
+ } catch (error) {
990
+ if (!isMethodNotFoundError(error)) {
991
+ throw error;
992
+ }
993
+ return [];
994
+ }
995
+ }
996
+ };
997
+ this.attachments = {
998
+ list: async (docId, options) => await this.rpc.call("attachments.list", {
999
+ databaseId: this.databaseId,
1000
+ docId,
1001
+ timestamp: options?.timestamp,
1002
+ revisionId: options?.revisionId
1003
+ }),
1004
+ remove: async (docId, attachmentName) => await this.rpc.call("attachments.remove", {
1005
+ databaseId: this.databaseId,
1006
+ docId,
1007
+ attachmentName
1008
+ }),
1009
+ openReadStream: async (docId, attachmentName, options) => {
1010
+ const result = await this.rpc.call(
1011
+ "attachments.openReadStream",
1012
+ {
1013
+ databaseId: this.databaseId,
1014
+ docId,
1015
+ attachmentName,
1016
+ timestamp: options?.timestamp,
1017
+ revisionId: options?.revisionId
1018
+ }
1019
+ );
1020
+ return new MindooDBAppReadableAttachmentStreamImpl(
1021
+ this.rpc,
1022
+ result.streamId
1023
+ );
1024
+ },
1025
+ openWriteStream: async (docId, attachmentName, contentType) => {
1026
+ const result = await this.rpc.call(
1027
+ "attachments.openWriteStream",
1028
+ {
1029
+ databaseId: this.databaseId,
1030
+ docId,
1031
+ attachmentName,
1032
+ contentType
1033
+ }
1034
+ );
1035
+ return new MindooDBAppWritableAttachmentStreamImpl(
1036
+ this.rpc,
1037
+ result.streamId
1038
+ );
1039
+ },
1040
+ scan: async (docId, options) => {
1041
+ const scannerTab = shouldUseWindowedHostTab() ? window.open("", "_blank") : null;
1042
+ if (scannerTab) {
1043
+ try {
1044
+ scannerTab.document.title = "Opening document scanner...";
1045
+ const preparedSession = await this.rpc.call("attachments.prepareScanSession", {
1046
+ databaseId: this.databaseId,
1047
+ docId,
1048
+ defaultFileName: options?.defaultFileName,
1049
+ preset: options?.preset,
1050
+ mimeType: options?.mimeType
1051
+ });
1052
+ scannerTab.location.href = preparedSession.scanUrl;
1053
+ return await this.rpc.call(
1054
+ "attachments.awaitScanSession",
1055
+ {
1056
+ databaseId: this.databaseId,
1057
+ sessionId: preparedSession.sessionId
1058
+ }
1059
+ );
1060
+ } catch (error) {
1061
+ scannerTab.close();
1062
+ throw error;
1063
+ }
1064
+ }
1065
+ return await this.rpc.call(
1066
+ "attachments.scanAndWrite",
1067
+ {
1068
+ databaseId: this.databaseId,
1069
+ docId,
1070
+ defaultFileName: options?.defaultFileName,
1071
+ preset: options?.preset,
1072
+ mimeType: options?.mimeType
1073
+ }
1074
+ );
1075
+ },
1076
+ preparePreviewSession: async (docId, attachmentName, options) => await this.rpc.call("attachments.preparePreviewSession", {
1077
+ databaseId: this.databaseId,
1078
+ docId,
1079
+ attachmentName,
1080
+ timestamp: options?.timestamp,
1081
+ revisionId: options?.revisionId
1082
+ }),
1083
+ openPreview: async (docId, attachmentName, options) => {
1084
+ const previewTab = shouldUseWindowedHostTab() ? window.open("", "_blank") : null;
1085
+ if (previewTab) {
1086
+ try {
1087
+ previewTab.document.title = "Opening attachment preview...";
1088
+ const previewSession = await this.rpc.call("attachments.preparePreviewSession", {
1089
+ databaseId: this.databaseId,
1090
+ docId,
1091
+ attachmentName,
1092
+ timestamp: options?.timestamp,
1093
+ revisionId: options?.revisionId
1094
+ });
1095
+ previewTab.location.href = previewSession.previewUrl;
1096
+ return { ok: true };
1097
+ } catch (error) {
1098
+ previewTab.close();
1099
+ throw error;
1100
+ }
1101
+ }
1102
+ return await this.rpc.call("attachments.openPreview", {
1103
+ databaseId: this.databaseId,
1104
+ docId,
1105
+ attachmentName,
1106
+ timestamp: options?.timestamp,
1107
+ revisionId: options?.revisionId
1108
+ });
1109
+ },
1110
+ extractText: async (input) => {
1111
+ return await this.rpc.call("attachments.extractText", {
1112
+ databaseId: this.databaseId,
1113
+ bytes: input.bytes,
1114
+ mimeType: input.mimeType,
1115
+ fileName: input.fileName,
1116
+ format: input.format,
1117
+ languages: input.languages
1118
+ });
1119
+ }
1120
+ };
1121
+ }
1122
+ rpc;
1123
+ databaseId;
1124
+ documents;
1125
+ attachments;
1126
+ identity;
1127
+ timestamps;
1128
+ directory;
1129
+ /** Returns database metadata for the currently opened binding. */
1130
+ async info() {
1131
+ return await this.rpc.call("databases.info", {
1132
+ databaseId: this.databaseId
1133
+ });
1134
+ }
1135
+ /** Reads the full-text configuration from the synced `dbsetup` document. */
1136
+ async getFulltextSetup() {
1137
+ const result = await this.rpc.call(
1138
+ "database.getFulltextSetup",
1139
+ { databaseId: this.databaseId }
1140
+ );
1141
+ return result.config;
1142
+ }
1143
+ /** Writes (or removes, with `null`) the full-text configuration. */
1144
+ async setFulltextSetup(config) {
1145
+ await this.rpc.call("database.setFulltextSetup", {
1146
+ databaseId: this.databaseId,
1147
+ config
1148
+ });
1149
+ }
1150
+ /** Reads the attachment extraction configuration from the synced `dbsetup` document. */
1151
+ async getExtractionSetup() {
1152
+ const result = await this.rpc.call(
1153
+ "database.getExtractionSetup",
1154
+ { databaseId: this.databaseId }
1155
+ );
1156
+ return result.config;
1157
+ }
1158
+ /** Writes (or removes, with `null`) the attachment extraction configuration. */
1159
+ async setExtractionSetup(config) {
1160
+ await this.rpc.call("database.setExtractionSetup", {
1161
+ databaseId: this.databaseId,
1162
+ config
1163
+ });
1164
+ }
1165
+ };
1166
+ var MindooDBAppSessionImpl = class {
1167
+ constructor(rpc, onDisconnect, hostShortcutsEnabled = true) {
1168
+ this.rpc = rpc;
1169
+ this.onDisconnect = onDisconnect;
1170
+ this.menus = {
1171
+ show: async (input) => await this.rpc.call("menus.show", input),
1172
+ hide: async () => {
1173
+ await this.rpc.call("menus.hide", {});
1174
+ }
1175
+ };
1176
+ this.storage = {
1177
+ snapshot: async (options) => await this.rpc.call("appStorage.snapshot", {
1178
+ prefixes: options?.prefixes
1179
+ }),
1180
+ get: async (key) => await this.rpc.call("appStorage.get", { key }),
1181
+ set: async (key, value) => {
1182
+ await this.rpc.call("appStorage.set", { key, value });
1183
+ },
1184
+ remove: async (key) => {
1185
+ await this.rpc.call("appStorage.remove", { key });
1186
+ },
1187
+ keys: async (options) => await this.rpc.call("appStorage.keys", { prefix: options?.prefix }),
1188
+ clear: async (options) => {
1189
+ await this.rpc.call("appStorage.clear", { prefix: options?.prefix });
1190
+ }
1191
+ };
1192
+ this.rpc.addMessageListener((message) => {
1193
+ if (message.kind === "before-close") {
1194
+ void this.handleBeforeClose(message.closeId);
1195
+ }
1196
+ if (message.kind === "shortcuts-changed") {
1197
+ const next = sanitizeHostShortcutBindings(message.shortcuts);
1198
+ if (next) {
1199
+ this.hostShortcuts = next;
1200
+ }
1201
+ }
1202
+ });
1203
+ this.stopHostShortcuts = hostShortcutsEnabled ? installHostShortcutCapture({
1204
+ getShortcuts: () => this.hostShortcuts,
1205
+ onInvoke: (action) => {
1206
+ if (!isHostShortcutAction(action)) {
1207
+ return;
1208
+ }
1209
+ try {
1210
+ this.rpc.postMessage({
1211
+ protocol: PROTOCOL2,
1212
+ kind: "shortcut-invoked",
1213
+ action
1214
+ });
1215
+ } catch (error) {
1216
+ console.warn("[mindoodb-app-sdk] Could not report a host shortcut.", error);
1217
+ }
1218
+ }
1219
+ }) : () => void 0;
1220
+ }
1221
+ rpc;
1222
+ onDisconnect;
1223
+ menus;
1224
+ storage;
1225
+ beforeCloseListeners = /* @__PURE__ */ new Set();
1226
+ hostShortcuts = DEFAULT_HAVEN_HOST_SHORTCUTS;
1227
+ stopHostShortcuts;
1228
+ /**
1229
+ * Lets every listener finish, then tells the host it may proceed.
1230
+ *
1231
+ * The ack is sent even when a listener fails: the host is going to tear the launch down
1232
+ * either way once its grace period elapses, and answering late only wastes that time.
1233
+ */
1234
+ async handleBeforeClose(closeId) {
1235
+ try {
1236
+ await Promise.all(
1237
+ [...this.beforeCloseListeners].map(async (listener) => {
1238
+ try {
1239
+ await listener();
1240
+ } catch (error) {
1241
+ console.warn("[mindoodb-app-sdk] A before-close listener failed.", error);
1242
+ }
1243
+ })
1244
+ );
1245
+ } finally {
1246
+ try {
1247
+ this.rpc.postMessage({
1248
+ protocol: PROTOCOL2,
1249
+ kind: "before-close-ack",
1250
+ closeId
1251
+ });
1252
+ } catch (error) {
1253
+ console.warn("[mindoodb-app-sdk] Could not acknowledge before-close.", error);
1254
+ }
1255
+ }
1256
+ }
1257
+ /** Returns the launch context provided by the Haven host. */
1258
+ async getLaunchContext() {
1259
+ return await this.rpc.call("session.getLaunchContext", {});
1260
+ }
1261
+ /**
1262
+ * Asks the host to open an encrypted channel to a tenant-joined service.
1263
+ * The host owns the key exchange and the resulting session key; nothing
1264
+ * secret crosses back into the app.
1265
+ */
1266
+ async openSealedChannel(input) {
1267
+ return await this.rpc.call("sealedChannel.open", input);
1268
+ }
1269
+ /** Sends one request through an open channel; the host seals and opens it. */
1270
+ async sealedChannelRequest(input) {
1271
+ return await this.rpc.call("sealedChannel.request", input);
1272
+ }
1273
+ /** Discards a channel host-side. */
1274
+ async closeSealedChannel(channelId) {
1275
+ await this.rpc.call("sealedChannel.close", { channelId });
1276
+ }
1277
+ /** Returns the product names currently licensed in the Haven host. */
1278
+ async getLicensedProducts() {
1279
+ return await this.rpc.call("session.getLicensedProducts", {});
1280
+ }
1281
+ /** Lists the databases that the host has made available for this app session. */
1282
+ async listDatabases() {
1283
+ return await this.rpc.call("session.listDatabases", {});
1284
+ }
1285
+ /** Opens one database binding and returns a client facade for it. */
1286
+ async openDatabase(databaseId) {
1287
+ await this.rpc.call("session.openDatabase", { databaseId });
1288
+ return new MindooDBAppDatabaseImpl(this.rpc, databaseId);
1289
+ }
1290
+ async listDocumentsSinceViewCursor(cursor) {
1291
+ return await this.rpc.call("session.listDocumentsSinceViewCursor", {
1292
+ cursor
1293
+ });
1294
+ }
1295
+ /**
1296
+ * Create a navigator from an ad-hoc view definition and one or more database bindings.
1297
+ *
1298
+ * The host builds a `VirtualView` from `input.definition`, optionally applying
1299
+ * open options (root scoping, category filtering), and returns a `navigatorId`
1300
+ * that all subsequent navigator RPCs reference.
1301
+ */
1302
+ async createViewNavigator(input) {
1303
+ const result = await this.rpc.call("session.createViewNavigator", input);
1304
+ return new MindooDBAppViewNavigatorImpl(
1305
+ this.rpc,
1306
+ result.navigatorId,
1307
+ result.viewCursor
1308
+ );
1309
+ }
1310
+ /**
1311
+ * Open a navigator for a preconfigured view that the host already knows about.
1312
+ *
1313
+ * `viewId` must match one of the views declared in the launch context. The
1314
+ * host resolves the full view definition and database bindings automatically.
1315
+ */
1316
+ async openViewNavigator(viewId, options) {
1317
+ const result = await this.rpc.call("session.openViewNavigator", { viewId, options: options ?? {} });
1318
+ return new MindooDBAppViewNavigatorImpl(
1319
+ this.rpc,
1320
+ result.navigatorId,
1321
+ result.viewCursor
1322
+ );
1323
+ }
1324
+ /** Subscribe to host-pushed theme changes (dark/light mode, color tokens). */
1325
+ onThemeChange(listener) {
1326
+ return this.rpc.addMessageListener((message) => {
1327
+ if (isThemeChangedMessage(message)) {
1328
+ listener(message.theme);
1329
+ }
1330
+ });
1331
+ }
1332
+ /** Subscribe to host-pushed viewport dimension changes (resize, orientation). */
1333
+ onViewportChange(listener) {
1334
+ return this.rpc.addMessageListener((message) => {
1335
+ if (isViewportChangedMessage(message)) {
1336
+ listener(message.viewport);
1337
+ }
1338
+ });
1339
+ }
1340
+ /** Subscribe to host-pushed embedded UI preference changes. */
1341
+ onUiPreferencesChange(listener) {
1342
+ return this.rpc.addMessageListener((message) => {
1343
+ if (isUiPreferencesChangedMessage(message)) {
1344
+ listener(message.uiPreferences);
1345
+ }
1346
+ });
1347
+ }
1348
+ /** Subscribe to host-pushed UI language changes. */
1349
+ onLocaleChange(listener) {
1350
+ return this.rpc.addMessageListener((message) => {
1351
+ if (isLocaleChangedMessage(message)) {
1352
+ listener(message.locale);
1353
+ }
1354
+ });
1355
+ }
1356
+ /** Register work to run before the host tears this launch down. */
1357
+ onBeforeClose(listener) {
1358
+ this.beforeCloseListeners.add(listener);
1359
+ return () => {
1360
+ this.beforeCloseListeners.delete(listener);
1361
+ };
1362
+ }
1363
+ /** Disconnects from the host and always disposes the underlying port client. */
1364
+ async disconnect() {
1365
+ this.stopHostShortcuts();
1366
+ try {
1367
+ await this.rpc.call("session.disconnect", {});
1368
+ } finally {
1369
+ this.rpc.dispose();
1370
+ this.onDisconnect?.();
1371
+ }
1372
+ }
1373
+ };
1374
+ var sessionsByLaunchId = /* @__PURE__ */ new Map();
1375
+ function releaseMindooDBAppBridgeSessions(launchId) {
1376
+ if (launchId === void 0) {
1377
+ sessionsByLaunchId.clear();
1378
+ return;
1379
+ }
1380
+ sessionsByLaunchId.delete(launchId);
1381
+ }
1382
+ function createMindooDBAppBridge() {
1383
+ return {
1384
+ async connect(options) {
1385
+ const launchId = resolveLaunchId(options);
1386
+ const existing = sessionsByLaunchId.get(launchId);
1387
+ if (existing) {
1388
+ return await existing;
1389
+ }
1390
+ const pending = (async () => {
1391
+ const port = await waitForConnectedPort(options, launchId);
1392
+ applyHostedDocumentOverscrollContain();
1393
+ const rpc = new PortRpcClient(port);
1394
+ return new MindooDBAppSessionImpl(
1395
+ rpc,
1396
+ () => {
1397
+ if (sessionsByLaunchId.get(launchId) === pending) {
1398
+ sessionsByLaunchId.delete(launchId);
1399
+ }
1400
+ },
1401
+ options?.hostShortcuts !== false
1402
+ );
1403
+ })();
1404
+ sessionsByLaunchId.set(launchId, pending);
1405
+ try {
1406
+ return await pending;
1407
+ } catch (error) {
1408
+ if (sessionsByLaunchId.get(launchId) === pending) {
1409
+ sessionsByLaunchId.delete(launchId);
1410
+ }
1411
+ throw error;
1412
+ }
1413
+ }
1414
+ };
1415
+ }
1416
+
1417
+ export {
1418
+ DEFAULT_HAVEN_HOST_SHORTCUTS,
1419
+ matchHostShortcut,
1420
+ isHostShortcutAction,
1421
+ sanitizeHostShortcutBindings,
1422
+ applyHostedDocumentOverscrollContain,
1423
+ installHostShortcutCapture,
1424
+ releaseMindooDBAppBridgeSessions,
1425
+ createMindooDBAppBridge
1426
+ };