mindoodb-app-sdk 0.0.40 → 0.0.42

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