mindoodb-app-sdk 0.0.57 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  MINDOODB_APP_VALUE_TAG,
3
+ assertValidMindooDBCreateIds,
3
4
  pageUsers,
4
5
  releaseMindooDBAppBridgeSessions
5
- } from "../chunk-S73KTYZF.js";
6
+ } from "../chunk-SKPP4T6S.js";
6
7
 
7
8
  // src/testing/index.ts
8
9
  import * as Automerge from "@automerge/automerge";
@@ -17,7 +18,7 @@ import { evaluateExpression, getFieldValue } from "mindoodb-view-language";
17
18
  var mindoodbModulePromise = null;
18
19
  async function loadMindoodbForTesting() {
19
20
  if (!mindoodbModulePromise) {
20
- mindoodbModulePromise = import("mindoodb").then((mod) => mod).catch(() => null);
21
+ mindoodbModulePromise = import("mindoodb").then((mod) => typeof mod.VirtualViewFactory === "function" ? mod : null).catch(() => null);
21
22
  }
22
23
  return mindoodbModulePromise;
23
24
  }
@@ -1583,6 +1584,7 @@ function mergeLaunchContext(current, patch) {
1583
1584
  uiPreferences: { ...current.uiPreferences },
1584
1585
  user: { ...current.user },
1585
1586
  appPermissions: [...current.appPermissions ?? []],
1587
+ browserFeatures: current.browserFeatures ? { ...current.browserFeatures } : void 0,
1586
1588
  launchParameters: { ...current.launchParameters },
1587
1589
  databases: current.databases.map((database) => ({
1588
1590
  ...database,
@@ -1617,6 +1619,7 @@ function mergeLaunchContext(current, patch) {
1617
1619
  uiPreferences: patch.uiPreferences ? { ...current.uiPreferences, ...patch.uiPreferences } : { ...current.uiPreferences },
1618
1620
  user: patch.user ? { ...current.user, ...patch.user } : { ...current.user },
1619
1621
  appPermissions: [...patch.appPermissions ?? current.appPermissions ?? []],
1622
+ browserFeatures: patch.browserFeatures ? { ...current.browserFeatures, ...patch.browserFeatures } : current.browserFeatures ? { ...current.browserFeatures } : void 0,
1620
1623
  launchParameters: patch.launchParameters ? { ...current.launchParameters, ...patch.launchParameters } : { ...current.launchParameters },
1621
1624
  databases: patch.databases ? patch.databases.map((database) => ({
1622
1625
  ...database,
@@ -1691,6 +1694,16 @@ function createDefaultLaunchContext(patch) {
1691
1694
  },
1692
1695
  licensedProducts: [],
1693
1696
  appPermissions: [],
1697
+ browserFeatures: {
1698
+ popups: false,
1699
+ camera: false,
1700
+ microphone: false,
1701
+ geolocation: false,
1702
+ clipboardWrite: false,
1703
+ motion: false,
1704
+ webRtc: false,
1705
+ workers: false
1706
+ },
1694
1707
  launchParameters: {},
1695
1708
  databases: [],
1696
1709
  views: []
@@ -1772,6 +1785,20 @@ function matchesDocumentFilter(document2, filter) {
1772
1785
  ([field, expected]) => getFieldValue2(document2.data, field) === expected
1773
1786
  );
1774
1787
  }
1788
+ function withValidatedCreateIds(documents) {
1789
+ const { create, createMany } = documents;
1790
+ return {
1791
+ ...documents,
1792
+ async create(input) {
1793
+ assertValidMindooDBCreateIds(input, "createDocument");
1794
+ return await create(input);
1795
+ },
1796
+ async createMany(inputs) {
1797
+ for (const input of inputs) assertValidMindooDBCreateIds(input, "createDocument");
1798
+ return await createMany(inputs);
1799
+ }
1800
+ };
1801
+ }
1775
1802
  function matchesMockIdPrefix(docId, idPrefix) {
1776
1803
  if (!idPrefix) {
1777
1804
  return true;
@@ -1854,7 +1881,7 @@ function computeMockTextScore(document2, clause) {
1854
1881
  const matches = combineWith === "AND" ? matchedTerms === terms.length : matchedTerms > 0;
1855
1882
  return matches ? score : void 0;
1856
1883
  }
1857
- function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabase) {
1884
+ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabase, summarySetup) {
1858
1885
  const filter = typeof query?.filter === "string" ? parseMindooDBFormulaBooleanExpression(query.filter) : query?.filter;
1859
1886
  const sortBy = query?.sortBy && query.sortBy.length > 0 ? query.sortBy : query?.text ? [{ special: "textScore", direction: "descending" }] : [];
1860
1887
  const matches = [];
@@ -1908,7 +1935,10 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1908
1935
  const page = matches.slice(offset, offset + limit);
1909
1936
  const rows = page.map(({ document: document2, textScore }) => ({
1910
1937
  docId: document2.id,
1911
- fields: projectMockFields(document2.data, query?.fields),
1938
+ fields: projectMockFields(
1939
+ mockSummaryDocument(document2.data, summarySetup),
1940
+ query?.fields
1941
+ ),
1912
1942
  lastModified: mockLastModified(document2),
1913
1943
  ...textScore === void 0 ? {} : { textScore }
1914
1944
  }));
@@ -1929,6 +1959,118 @@ function runMockDocumentQuery(storedDocuments, databaseId, query, resolveDatabas
1929
1959
  }
1930
1960
  return { rows, total: matches.length, coverage: "full" };
1931
1961
  }
1962
+ function extractMockSummaryFields(data, setup) {
1963
+ const autoInclude = setup?.autoInclude ?? true;
1964
+ const maxValueBytes = Math.max(0, setup?.maxValueBytes ?? 1024);
1965
+ const include = setup?.include ?? [];
1966
+ const exclude = setup?.exclude ?? [];
1967
+ const isExcluded = (path) => exclude.some((excluded) => path === excluded || path.startsWith(`${excluded}.`));
1968
+ const isScalar = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1969
+ const valueSize = (value) => {
1970
+ if (typeof value === "string") return value.length + 2;
1971
+ try {
1972
+ return JSON.stringify(value)?.length ?? 0;
1973
+ } catch {
1974
+ return Number.POSITIVE_INFINITY;
1975
+ }
1976
+ };
1977
+ const fields = {};
1978
+ if (autoInclude) {
1979
+ for (const [key, value] of Object.entries(data)) {
1980
+ if (key.startsWith("_") || key.endsWith("_encrypted") || key.endsWith("_encrypted_key")) {
1981
+ continue;
1982
+ }
1983
+ if (isExcluded(key)) continue;
1984
+ const includable = isScalar(value) || Array.isArray(value) && value.every(isScalar);
1985
+ if (!includable || valueSize(value) > maxValueBytes) continue;
1986
+ fields[key] = structuredClone(value);
1987
+ }
1988
+ }
1989
+ for (const path of include) {
1990
+ if (isExcluded(path)) continue;
1991
+ const value = getFieldValue2(data, path);
1992
+ if (value !== void 0) fields[path] = structuredClone(value);
1993
+ }
1994
+ if ((setup?.includeAttachments ?? true) && !isExcluded("_attachments")) {
1995
+ const attachments = data._attachments;
1996
+ if (Array.isArray(attachments) && attachments.length > 0) {
1997
+ fields._attachments = attachments.filter(
1998
+ (item2) => item2 !== null && typeof item2 === "object"
1999
+ ).map((item2) => {
2000
+ const info = {};
2001
+ for (const key of ["attachmentId", "fileName", "size", "mimeType", "createdAt"]) {
2002
+ if (item2[key] !== void 0) info[key] = item2[key];
2003
+ }
2004
+ if (typeof item2.extractedText === "string" && item2.extractedText.length > 0) {
2005
+ info.hasExtractedText = true;
2006
+ }
2007
+ return info;
2008
+ });
2009
+ }
2010
+ }
2011
+ return fields;
2012
+ }
2013
+ function mockSummaryDocument(data, setup) {
2014
+ const doc = {};
2015
+ for (const [key, value] of Object.entries(extractMockSummaryFields(data, setup))) {
2016
+ const parts = key.split(".");
2017
+ let current = doc;
2018
+ for (const part of parts.slice(0, -1)) {
2019
+ const next = current[part];
2020
+ if (next !== null && typeof next === "object" && !Array.isArray(next)) {
2021
+ current = next;
2022
+ } else {
2023
+ const created = {};
2024
+ current[part] = created;
2025
+ current = created;
2026
+ }
2027
+ }
2028
+ current[parts[parts.length - 1]] = value;
2029
+ }
2030
+ return doc;
2031
+ }
2032
+ function createLiveMockViewNavigator(initial, rebuild, subscribe) {
2033
+ let inner = initial;
2034
+ let stale = false;
2035
+ let pendingNotify = null;
2036
+ const listeners = /* @__PURE__ */ new Set();
2037
+ const unsubscribe = subscribe(() => {
2038
+ stale = true;
2039
+ if (pendingNotify) return;
2040
+ pendingNotify = setTimeout(() => {
2041
+ pendingNotify = null;
2042
+ for (const listener of listeners) listener({ addedCount: 0, removedCount: 0 });
2043
+ }, 0);
2044
+ });
2045
+ const current = async () => {
2046
+ if (stale) {
2047
+ stale = false;
2048
+ inner = await rebuild() ?? inner;
2049
+ }
2050
+ return inner;
2051
+ };
2052
+ return new Proxy({}, {
2053
+ get(_target, property) {
2054
+ if (property === "then") return void 0;
2055
+ if (property === "onDidUpdate") {
2056
+ return (listener) => {
2057
+ listeners.add(listener);
2058
+ return () => listeners.delete(listener);
2059
+ };
2060
+ }
2061
+ return async (...args) => {
2062
+ if (property === "dispose") {
2063
+ unsubscribe();
2064
+ listeners.clear();
2065
+ if (pendingNotify) clearTimeout(pendingNotify);
2066
+ }
2067
+ const navigator = await current();
2068
+ const member = navigator[property];
2069
+ return typeof member === "function" ? member.apply(navigator, args) : member;
2070
+ };
2071
+ }
2072
+ });
2073
+ }
1932
2074
  function projectMockFields(data, fields) {
1933
2075
  return fields ? Object.fromEntries(fields.map((field) => [field, getFieldValue2(data, field)])) : structuredClone(data);
1934
2076
  }
@@ -2484,7 +2626,8 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
2484
2626
  storedDocuments,
2485
2627
  definition.info.id,
2486
2628
  subscription.query,
2487
- peers.getDocuments
2629
+ peers.getDocuments,
2630
+ summarySetup
2488
2631
  );
2489
2632
  const resultJson = JSON.stringify(result);
2490
2633
  if (resultJson !== subscription.lastResultJson) {
@@ -2495,6 +2638,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
2495
2638
  };
2496
2639
  const notifyLiveQueries = () => {
2497
2640
  peers.refreshAllLiveQueries();
2641
+ peers.documentsChanged(definition.info.id);
2498
2642
  };
2499
2643
  function mockEncryptForMap(names) {
2500
2644
  const encryptFor = {};
@@ -2540,7 +2684,8 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
2540
2684
  storedDocuments,
2541
2685
  definition.info.id,
2542
2686
  query,
2543
- peers.getDocuments
2687
+ peers.getDocuments,
2688
+ summarySetup
2544
2689
  );
2545
2690
  },
2546
2691
  async liveQuery(query, onResult) {
@@ -2548,7 +2693,8 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
2548
2693
  storedDocuments,
2549
2694
  definition.info.id,
2550
2695
  query,
2551
- peers.getDocuments
2696
+ peers.getDocuments,
2697
+ summarySetup
2552
2698
  );
2553
2699
  const subscription = {
2554
2700
  query,
@@ -2566,7 +2712,8 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
2566
2712
  storedDocuments,
2567
2713
  definition.info.id,
2568
2714
  query,
2569
- peers.getDocuments
2715
+ peers.getDocuments,
2716
+ summarySetup
2570
2717
  );
2571
2718
  subscription.lastResultJson = JSON.stringify(result);
2572
2719
  onResult(result);
@@ -3089,10 +3236,10 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
3089
3236
  capabilities: [...definition.info.capabilities]
3090
3237
  };
3091
3238
  },
3092
- documents: {
3239
+ documents: withValidatedCreateIds({
3093
3240
  ...defaultDocuments,
3094
3241
  ...methods.documents
3095
- },
3242
+ }),
3096
3243
  attachments: {
3097
3244
  ...defaultAttachments,
3098
3245
  ...methods.attachments
@@ -3143,6 +3290,9 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
3143
3290
  function createMockSessionState(options = {}) {
3144
3291
  let launchContext = createDefaultLaunchContext(options.launchContext);
3145
3292
  const themeListeners = /* @__PURE__ */ new Set();
3293
+ let incomingHandler = null;
3294
+ const pendingIncoming = [];
3295
+ let incomingCounter = 0;
3146
3296
  const hostFocusListeners = /* @__PURE__ */ new Set();
3147
3297
  let hostFocused = false;
3148
3298
  const viewportListeners = /* @__PURE__ */ new Set();
@@ -3159,8 +3309,14 @@ function createMockSessionState(options = {}) {
3159
3309
  for (const refresh of databaseLiveQueryRefreshers.values()) {
3160
3310
  refresh();
3161
3311
  }
3312
+ },
3313
+ documentsChanged: (databaseId) => {
3314
+ for (const listener of viewChangeListeners) {
3315
+ if (listener.databaseIds.includes(databaseId)) listener.onChange();
3316
+ }
3162
3317
  }
3163
3318
  };
3319
+ const viewChangeListeners = /* @__PURE__ */ new Set();
3164
3320
  const databaseViewApis = /* @__PURE__ */ new Map();
3165
3321
  const sessionViews = /* @__PURE__ */ new Map();
3166
3322
  let activeMenuResolve = null;
@@ -3257,16 +3413,30 @@ function createMockSessionState(options = {}) {
3257
3413
  databaseLiveQueryRefreshers.set(definition.info.id, created.notifyLiveQueries);
3258
3414
  databaseViewApis.set(definition.info.id, {
3259
3415
  async create(input) {
3260
- const documents = input.databaseIds.flatMap((databaseId2) => {
3261
- const list = databaseViewDocumentLists.get(databaseId2);
3262
- return list ? list() : [];
3263
- });
3264
- const evaluating = await createEvaluatingViewNavigator(input, documents);
3265
- if (evaluating) return evaluating;
3416
+ const build = () => createEvaluatingViewNavigator(
3417
+ input,
3418
+ input.databaseIds.flatMap((databaseId2) => {
3419
+ const list = databaseViewDocumentLists.get(databaseId2);
3420
+ return list ? list() : [];
3421
+ })
3422
+ );
3423
+ const subscribe = (onChange) => {
3424
+ const listener = { databaseIds: [...input.databaseIds], onChange };
3425
+ viewChangeListeners.add(listener);
3426
+ return () => viewChangeListeners.delete(listener);
3427
+ };
3428
+ const evaluating = await build();
3429
+ if (evaluating) {
3430
+ return createLiveMockViewNavigator(evaluating, build, subscribe);
3431
+ }
3266
3432
  console.warn(
3267
3433
  "[mindoodb-app-sdk/testing] mindoodb peer not available; using empty view navigator. Install mindoodb as a devDependency for evaluating VirtualViews."
3268
3434
  );
3269
- return await createDefaultViewNavigator();
3435
+ return createLiveMockViewNavigator(
3436
+ await createDefaultViewNavigator(),
3437
+ async () => null,
3438
+ subscribe
3439
+ );
3270
3440
  },
3271
3441
  async open(_viewId) {
3272
3442
  return await createDefaultViewNavigator();
@@ -3448,6 +3618,15 @@ function createMockSessionState(options = {}) {
3448
3618
  localeListeners.delete(listener);
3449
3619
  };
3450
3620
  },
3621
+ onIncomingContent(handler) {
3622
+ incomingHandler = handler;
3623
+ pendingIncoming.splice(0).forEach((deliver) => deliver());
3624
+ return () => {
3625
+ if (incomingHandler === handler) {
3626
+ incomingHandler = null;
3627
+ }
3628
+ };
3629
+ },
3451
3630
  onBeforeClose(listener) {
3452
3631
  beforeCloseListeners.add(listener);
3453
3632
  return () => {
@@ -3519,6 +3698,21 @@ function createMockSessionState(options = {}) {
3519
3698
  openViewNavigator: session.openViewNavigator,
3520
3699
  bridge,
3521
3700
  session,
3701
+ emitIncomingContent(input) {
3702
+ incomingCounter += 1;
3703
+ const content = buildMockIncomingContent(input, `mock-delivery-${incomingCounter}`);
3704
+ return new Promise((resolve, reject) => {
3705
+ const deliver = () => {
3706
+ const handler = incomingHandler;
3707
+ if (!handler) {
3708
+ pendingIncoming.push(deliver);
3709
+ return;
3710
+ }
3711
+ Promise.resolve().then(() => handler(content)).then((result) => resolve(result ?? { status: "accepted" }), reject);
3712
+ };
3713
+ deliver();
3714
+ });
3715
+ },
3522
3716
  emitThemeChange(theme) {
3523
3717
  launchContext = mergeLaunchContext(launchContext, { theme });
3524
3718
  themeListeners.forEach(
@@ -3564,6 +3758,40 @@ function generateDirectoryUsers(count, options = {}) {
3564
3758
  (_, index) => `CN=${prefix} ${String(index + 1).padStart(width, "0")}/O=${organization}`
3565
3759
  );
3566
3760
  }
3761
+ function buildMockIncomingContent(input, deliveryId) {
3762
+ return {
3763
+ deliveryId,
3764
+ acceptId: input.acceptId ?? "default",
3765
+ source: input.source ?? "picker",
3766
+ items: input.items.map((entry, index) => {
3767
+ const itemId = `${deliveryId}-${index + 1}`;
3768
+ if ("file" in entry) {
3769
+ const name = entry.path ?? entry.name ?? (entry.file.name || `file-${index + 1}`);
3770
+ return {
3771
+ itemId,
3772
+ path: name,
3773
+ kind: "file",
3774
+ type: entry.file.type || "application/octet-stream",
3775
+ size: entry.file.size,
3776
+ read: async () => entry.file,
3777
+ readText: async () => await entry.file.text()
3778
+ };
3779
+ }
3780
+ const isUrl = "url" in entry;
3781
+ const text = isUrl ? entry.url : entry.text;
3782
+ return {
3783
+ itemId,
3784
+ path: entry.path ?? (isUrl ? "link.url" : "text.txt"),
3785
+ kind: isUrl ? "url" : "text",
3786
+ type: isUrl ? "text/uri-list" : "text/plain",
3787
+ size: new Blob([text]).size,
3788
+ text,
3789
+ read: async () => new Blob([text], { type: isUrl ? "text/uri-list" : "text/plain" }),
3790
+ readText: async () => text
3791
+ };
3792
+ })
3793
+ };
3794
+ }
3567
3795
  function createMockMindooDBAppSession(options = {}) {
3568
3796
  const state = createMockSessionState(options);
3569
3797
  return {
@@ -3587,7 +3815,8 @@ function createMockMindooDBAppSession(options = {}) {
3587
3815
  state.setHostFocused(focused);
3588
3816
  },
3589
3817
  resolveDrag: state.resolveDrag,
3590
- getDragProfile: state.getDragProfile
3818
+ getDragProfile: state.getDragProfile,
3819
+ emitIncomingContent: state.emitIncomingContent
3591
3820
  };
3592
3821
  }
3593
3822
  function createMockMindooDBAppBridge(options = {}) {
@@ -3777,6 +4006,16 @@ function createFakeBridgeHost(options = {}) {
3777
4006
  options: params.options
3778
4007
  });
3779
4008
  viewSessions.set(navigatorId, navigator);
4009
+ navigator.onDidUpdate((stats) => {
4010
+ if (!viewSessions.has(navigatorId)) return;
4011
+ const payload = {
4012
+ protocol: PROTOCOL,
4013
+ kind: "view-changed",
4014
+ navigatorId,
4015
+ stats
4016
+ };
4017
+ connectedPorts.forEach((port) => port.postMessage(payload));
4018
+ });
3780
4019
  return { navigatorId };
3781
4020
  }
3782
4021
  case "session.openViewNavigator": {