mindoodb-app-sdk 0.0.19 → 0.0.26

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
@@ -80,6 +80,9 @@ function expandAbbreviatedName(value) {
80
80
  ].join("/");
81
81
  }
82
82
 
83
+ // src/client/createMindooDBAppBridge.ts
84
+ import { parseMindooDBFormulaBooleanExpression } from "mindoodb-view-language";
85
+
83
86
  // src/client/portRpcClient.ts
84
87
  var PROTOCOL = "mindoodb-app-bridge";
85
88
  function normalizeRpcValue(value, seen = /* @__PURE__ */ new WeakMap()) {
@@ -232,6 +235,9 @@ var PortRpcClient = class {
232
235
  // src/client/createMindooDBAppBridge.ts
233
236
  var PROTOCOL2 = "mindoodb-app-bridge";
234
237
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
238
+ function isMethodNotFoundError(error) {
239
+ return error instanceof Error && error.name === "method-not-found";
240
+ }
235
241
  function toTransferableArrayBuffer(chunk) {
236
242
  return chunk.buffer.slice(
237
243
  chunk.byteOffset,
@@ -247,6 +253,24 @@ function isStreamAckFor(streamId, message) {
247
253
  function isStreamErrorFor(streamId, message) {
248
254
  return message.kind === "stream-error" && message.streamId === streamId;
249
255
  }
256
+ function isQueryResultFor(subscriptionId, message) {
257
+ return message.kind === "query-result" && message.subscriptionId === subscriptionId;
258
+ }
259
+ function isViewChangedFor(navigatorId, message) {
260
+ return message.kind === "view-changed" && message.navigatorId === navigatorId;
261
+ }
262
+ function normalizeDocumentQuery(query) {
263
+ if (!query) {
264
+ return {};
265
+ }
266
+ const { filter, ...rest } = query;
267
+ return {
268
+ ...rest,
269
+ ...filter !== void 0 ? {
270
+ filter: typeof filter === "string" ? parseMindooDBFormulaBooleanExpression(filter) : filter
271
+ } : {}
272
+ };
273
+ }
250
274
  function isThemeChangedMessage(message) {
251
275
  return message.kind === "theme-changed";
252
276
  }
@@ -256,6 +280,9 @@ function isViewportChangedMessage(message) {
256
280
  function isUiPreferencesChangedMessage(message) {
257
281
  return message.kind === "ui-preferences-changed";
258
282
  }
283
+ function isLocaleChangedMessage(message) {
284
+ return message.kind === "locale-changed";
285
+ }
259
286
  function toStreamError(message) {
260
287
  const error = new Error(message.error.message);
261
288
  error.name = message.error.code || "MindooDBAppBridgeStreamError";
@@ -378,6 +405,13 @@ var MindooDBAppViewNavigatorImpl = class {
378
405
  this.viewCursor = result.viewCursor;
379
406
  return this.viewCursor;
380
407
  }
408
+ onDidUpdate(listener) {
409
+ return this.rpc.addMessageListener((message) => {
410
+ if (isViewChangedFor(this.navigatorId, message)) {
411
+ listener(message.stats);
412
+ }
413
+ });
414
+ }
381
415
  async getCurrentEntry() {
382
416
  return await this.call("viewNavigators.current.get");
383
417
  }
@@ -719,6 +753,44 @@ var MindooDBAppDatabaseImpl = class {
719
753
  databaseId: this.databaseId,
720
754
  query: query ?? {}
721
755
  }),
756
+ query: async (query) => await this.rpc.call("documents.query", {
757
+ databaseId: this.databaseId,
758
+ query: normalizeDocumentQuery(query)
759
+ }),
760
+ liveQuery: async (query, onResult) => {
761
+ const { subscriptionId, result } = await this.rpc.call("documents.liveQuery.subscribe", {
762
+ databaseId: this.databaseId,
763
+ query: normalizeDocumentQuery(query)
764
+ });
765
+ let disposed = false;
766
+ const unsubscribe = this.rpc.addMessageListener((message) => {
767
+ if (!disposed && isQueryResultFor(subscriptionId, message)) {
768
+ onResult(message.result);
769
+ }
770
+ });
771
+ onResult(result);
772
+ const subscription = {
773
+ refresh: async () => {
774
+ if (disposed) {
775
+ return;
776
+ }
777
+ await this.rpc.call("documents.liveQuery.refresh", {
778
+ subscriptionId
779
+ });
780
+ },
781
+ dispose: async () => {
782
+ if (disposed) {
783
+ return;
784
+ }
785
+ disposed = true;
786
+ unsubscribe();
787
+ await this.rpc.call("documents.liveQuery.unsubscribe", {
788
+ subscriptionId
789
+ });
790
+ }
791
+ };
792
+ return subscription;
793
+ },
722
794
  getHeadCursor: async () => await this.rpc.call("documents.getHeadCursor", {
723
795
  databaseId: this.databaseId
724
796
  }),
@@ -732,10 +804,44 @@ var MindooDBAppDatabaseImpl = class {
732
804
  path,
733
805
  revisionId: options?.revisionId
734
806
  }),
807
+ getAutomergeSnapshot: async (docId, options) => await this.rpc.call("documents.automerge.getSnapshot", {
808
+ databaseId: this.databaseId,
809
+ docId,
810
+ revisionId: options?.revisionId
811
+ }),
812
+ applyAutomergeChanges: async (docId, patch) => await this.rpc.call("documents.automerge.applyChanges", {
813
+ databaseId: this.databaseId,
814
+ docId,
815
+ patch
816
+ }),
735
817
  create: async (input) => await this.rpc.call("documents.create", {
736
818
  databaseId: this.databaseId,
737
819
  input
738
820
  }),
821
+ createMany: async (inputs) => {
822
+ if (inputs.length === 0) {
823
+ return { ids: [] };
824
+ }
825
+ try {
826
+ return await this.rpc.call("documents.createMany", {
827
+ databaseId: this.databaseId,
828
+ inputs
829
+ });
830
+ } catch (error) {
831
+ if (!isMethodNotFoundError(error)) {
832
+ throw error;
833
+ }
834
+ const ids = [];
835
+ for (const input of inputs) {
836
+ const document = await this.rpc.call("documents.create", {
837
+ databaseId: this.databaseId,
838
+ input
839
+ });
840
+ ids.push(document.id);
841
+ }
842
+ return { ids };
843
+ }
844
+ },
739
845
  update: async (docId, patch) => await this.rpc.call("documents.update", {
740
846
  databaseId: this.databaseId,
741
847
  docId,
@@ -745,10 +851,51 @@ var MindooDBAppDatabaseImpl = class {
745
851
  databaseId: this.databaseId,
746
852
  docId
747
853
  }),
854
+ deleteMany: async (docIds) => {
855
+ if (docIds.length === 0) {
856
+ return { ok: true };
857
+ }
858
+ try {
859
+ return await this.rpc.call("documents.deleteMany", {
860
+ databaseId: this.databaseId,
861
+ docIds
862
+ });
863
+ } catch (error) {
864
+ if (!isMethodNotFoundError(error)) {
865
+ throw error;
866
+ }
867
+ for (const docId of docIds) {
868
+ await this.rpc.call("documents.delete", {
869
+ databaseId: this.databaseId,
870
+ docId
871
+ });
872
+ }
873
+ return { ok: true };
874
+ }
875
+ },
748
876
  undelete: async (docId) => await this.rpc.call("documents.undelete", {
749
877
  databaseId: this.databaseId,
750
878
  docId
751
879
  }),
880
+ canCreate: async (input) => await this.rpc.call("documents.canCreate", {
881
+ databaseId: this.databaseId,
882
+ input: input ?? {}
883
+ }),
884
+ canChange: async (docId) => await this.rpc.call("documents.canChange", {
885
+ databaseId: this.databaseId,
886
+ docId
887
+ }),
888
+ canDelete: async (docId) => await this.rpc.call("documents.canDelete", {
889
+ databaseId: this.databaseId,
890
+ docId
891
+ }),
892
+ canUndelete: async (docId) => await this.rpc.call("documents.canUndelete", {
893
+ databaseId: this.databaseId,
894
+ docId
895
+ }),
896
+ getDefaultCreateKeyId: async () => await this.rpc.call("documents.getDefaultCreateKeyId", {
897
+ databaseId: this.databaseId
898
+ }),
752
899
  listHistory: async (docId) => await this.rpc.call("documents.history.list", {
753
900
  databaseId: this.databaseId,
754
901
  docId
@@ -889,6 +1036,36 @@ var MindooDBAppDatabaseImpl = class {
889
1036
  databaseId: this.databaseId
890
1037
  });
891
1038
  }
1039
+ /** Reads the full-text configuration from the synced `dbsetup` document. */
1040
+ async getFulltextSetup() {
1041
+ const result = await this.rpc.call(
1042
+ "database.getFulltextSetup",
1043
+ { databaseId: this.databaseId }
1044
+ );
1045
+ return result.config;
1046
+ }
1047
+ /** Writes (or removes, with `null`) the full-text configuration. */
1048
+ async setFulltextSetup(config) {
1049
+ await this.rpc.call("database.setFulltextSetup", {
1050
+ databaseId: this.databaseId,
1051
+ config
1052
+ });
1053
+ }
1054
+ /** Reads the attachment extraction configuration from the synced `dbsetup` document. */
1055
+ async getExtractionSetup() {
1056
+ const result = await this.rpc.call(
1057
+ "database.getExtractionSetup",
1058
+ { databaseId: this.databaseId }
1059
+ );
1060
+ return result.config;
1061
+ }
1062
+ /** Writes (or removes, with `null`) the attachment extraction configuration. */
1063
+ async setExtractionSetup(config) {
1064
+ await this.rpc.call("database.setExtractionSetup", {
1065
+ databaseId: this.databaseId,
1066
+ config
1067
+ });
1068
+ }
892
1069
  };
893
1070
  var MindooDBAppSessionImpl = class {
894
1071
  constructor(rpc) {
@@ -906,6 +1083,10 @@ var MindooDBAppSessionImpl = class {
906
1083
  async getLaunchContext() {
907
1084
  return await this.rpc.call("session.getLaunchContext", {});
908
1085
  }
1086
+ /** Returns the product names currently licensed in the Haven host. */
1087
+ async getLicensedProducts() {
1088
+ return await this.rpc.call("session.getLicensedProducts", {});
1089
+ }
909
1090
  /** Lists the databases that the host has made available for this app session. */
910
1091
  async listDatabases() {
911
1092
  return await this.rpc.call("session.listDatabases", {});
@@ -973,6 +1154,14 @@ var MindooDBAppSessionImpl = class {
973
1154
  }
974
1155
  });
975
1156
  }
1157
+ /** Subscribe to host-pushed UI language changes. */
1158
+ onLocaleChange(listener) {
1159
+ return this.rpc.addMessageListener((message) => {
1160
+ if (isLocaleChangedMessage(message)) {
1161
+ listener(message.locale);
1162
+ }
1163
+ });
1164
+ }
976
1165
  /** Disconnects from the host and always disposes the underlying port client. */
977
1166
  async disconnect() {
978
1167
  try {
@@ -997,11 +1186,18 @@ function createMindooDBAppBridge() {
997
1186
  var MindooDBRichTextHandle = class {
998
1187
  constructor(options) {
999
1188
  this.options = options;
1000
- this.spansInternal = cloneSpans(options.spans ?? []);
1189
+ const initialSpans = cloneSpans(options.spans ?? []);
1190
+ this.spansInternal = initialSpans;
1191
+ this.baseMaterializedText = readMaterializedRichTextText(
1192
+ options.document.data,
1193
+ options.path,
1194
+ initialSpans
1195
+ );
1001
1196
  this.heads = options.document.heads ? [...options.document.heads] : [];
1002
1197
  }
1003
1198
  options;
1004
1199
  spansInternal;
1200
+ baseMaterializedText;
1005
1201
  heads;
1006
1202
  dirtyInternal = false;
1007
1203
  listeners = /* @__PURE__ */ new Set();
@@ -1041,6 +1237,7 @@ var MindooDBRichTextHandle = class {
1041
1237
  reconcile(document, snapshot) {
1042
1238
  const previous = JSON.stringify(this.spansInternal);
1043
1239
  this.spansInternal = cloneSpans(snapshot.spans);
1240
+ this.baseMaterializedText = spansToPlainText(snapshot.spans);
1044
1241
  this.heads = snapshot.heads ?? document.heads ?? [];
1045
1242
  this.dirtyInternal = false;
1046
1243
  const changed = JSON.stringify(this.spansInternal) !== previous;
@@ -1081,14 +1278,16 @@ var MindooDBRichTextHandle = class {
1081
1278
  reconciled: false
1082
1279
  };
1083
1280
  }
1084
- const localSpansAtFlush = JSON.stringify(this.spansInternal);
1085
- const document = await this.options.database.documents.update(
1281
+ const localSpansAtFlush = cloneSpans(this.spansInternal);
1282
+ const localTextAtFlush = spansToPlainText(localSpansAtFlush);
1283
+ const textChanged = localTextAtFlush !== this.baseMaterializedText;
1284
+ const document = textChanged ? await this.flushTextSteps(localTextAtFlush) : await this.options.database.documents.update(
1086
1285
  this.options.document.id,
1087
1286
  {
1088
1287
  richText: [{
1089
1288
  path: this.options.path,
1090
1289
  baseHeads: this.heads,
1091
- spans: cloneSpans(this.spansInternal),
1290
+ spans: localSpansAtFlush,
1092
1291
  updateSpansConfig
1093
1292
  }]
1094
1293
  }
@@ -1097,8 +1296,9 @@ var MindooDBRichTextHandle = class {
1097
1296
  this.options.document.id,
1098
1297
  this.options.path
1099
1298
  );
1100
- const reconciled = JSON.stringify(snapshot.spans) !== localSpansAtFlush;
1299
+ const reconciled = JSON.stringify(snapshot.spans) !== JSON.stringify(localSpansAtFlush);
1101
1300
  this.spansInternal = cloneSpans(snapshot.spans);
1301
+ this.baseMaterializedText = spansToPlainText(snapshot.spans);
1102
1302
  this.heads = snapshot.heads ?? document.heads ?? [];
1103
1303
  this.dirtyInternal = false;
1104
1304
  if (reconciled) {
@@ -1110,6 +1310,21 @@ var MindooDBRichTextHandle = class {
1110
1310
  reconciled
1111
1311
  };
1112
1312
  }
1313
+ async flushTextSteps(localTextAtFlush) {
1314
+ const edit = computeSingleSplice(this.baseMaterializedText, localTextAtFlush);
1315
+ return this.options.database.documents.update(this.options.document.id, {
1316
+ richTextSteps: [{
1317
+ path: this.options.path,
1318
+ baseHeads: this.heads,
1319
+ steps: [{
1320
+ type: "splice",
1321
+ index: edit.index,
1322
+ deleteCount: edit.deleteCount,
1323
+ insert: edit.insert ?? ""
1324
+ }]
1325
+ }]
1326
+ });
1327
+ }
1113
1328
  emitChange() {
1114
1329
  const snapshot = {
1115
1330
  path: [...this.options.path],
@@ -1127,6 +1342,44 @@ function createMindooDBRichTextHandle(options) {
1127
1342
  function cloneSpans(spans) {
1128
1343
  return structuredClone(spans);
1129
1344
  }
1345
+ function spansToPlainText(spans) {
1346
+ return spans.filter((span) => span.type === "text").map((span) => span.type === "text" ? span.value : "").join("");
1347
+ }
1348
+ function readMaterializedRichTextText(data, path, fallbackSpans) {
1349
+ let value = data;
1350
+ for (const segment of path) {
1351
+ if (value == null || typeof value !== "object") {
1352
+ return spansToPlainText(fallbackSpans);
1353
+ }
1354
+ value = value[segment];
1355
+ }
1356
+ if (typeof value === "string") {
1357
+ return value.replace(/\uFFFC/g, "");
1358
+ }
1359
+ if (Array.isArray(value)) {
1360
+ return spansToPlainText(value);
1361
+ }
1362
+ return spansToPlainText(fallbackSpans);
1363
+ }
1364
+ function computeSingleSplice(previous, next) {
1365
+ let prefixLength = 0;
1366
+ const maxPrefix = Math.min(previous.length, next.length);
1367
+ while (prefixLength < maxPrefix && previous[prefixLength] === next[prefixLength]) {
1368
+ prefixLength += 1;
1369
+ }
1370
+ let suffixLength = 0;
1371
+ const previousRemaining = previous.length - prefixLength;
1372
+ const nextRemaining = next.length - prefixLength;
1373
+ const maxSuffix = Math.min(previousRemaining, nextRemaining);
1374
+ while (suffixLength < maxSuffix && previous[previous.length - 1 - suffixLength] === next[next.length - 1 - suffixLength]) {
1375
+ suffixLength += 1;
1376
+ }
1377
+ return {
1378
+ index: prefixLength,
1379
+ deleteCount: previous.length - prefixLength - suffixLength,
1380
+ insert: next.slice(prefixLength, next.length - suffixLength)
1381
+ };
1382
+ }
1130
1383
 
1131
1384
  // src/textBuffer.ts
1132
1385
  var MindooDBTextBuffer = class {
@@ -1181,7 +1434,7 @@ var MindooDBTextBuffer = class {
1181
1434
  if (nextValue === this.valueInternal) {
1182
1435
  return;
1183
1436
  }
1184
- const edit = computeSingleSplice(this.valueInternal, nextValue);
1437
+ const edit = computeSingleSplice2(this.valueInternal, nextValue);
1185
1438
  this.splice(edit.index, edit.deleteCount, edit.insert ?? "");
1186
1439
  }
1187
1440
  /**
@@ -1248,7 +1501,7 @@ function readTextAtPath(data, path) {
1248
1501
  }
1249
1502
  return typeof value === "string" ? value : "";
1250
1503
  }
1251
- function computeSingleSplice(previous, next) {
1504
+ function computeSingleSplice2(previous, next) {
1252
1505
  let prefixLength = 0;
1253
1506
  const maxPrefix = Math.min(previous.length, next.length);
1254
1507
  while (prefixLength < maxPrefix && previous[prefixLength] === next[prefixLength]) {
@@ -1269,12 +1522,20 @@ function computeSingleSplice(previous, next) {
1269
1522
  }
1270
1523
 
1271
1524
  // src/viewLanguage.ts
1525
+ import { parseMindooDBFormulaBooleanExpression as parseMindooDBFormulaBooleanExpression2 } from "mindoodb-view-language";
1272
1526
  import {
1273
1527
  createViewLanguage,
1274
1528
  getMindooDBViewLanguageHelper,
1275
1529
  mindooDBViewLanguageHelpers,
1276
1530
  mindooDBViewLanguageHelpersByName
1277
1531
  } from "mindoodb-view-language";
1532
+ async function queryDocuments(db, filter, sortBy, options) {
1533
+ if (!db.query) {
1534
+ throw new Error("This MindooDB instance does not support ad-hoc queries.");
1535
+ }
1536
+ const expression = typeof filter === "string" ? parseMindooDBFormulaBooleanExpression2(filter) : filter;
1537
+ return db.query({ filter: expression, sortBy }, options);
1538
+ }
1278
1539
  export {
1279
1540
  MindooDBRichTextHandle,
1280
1541
  MindooDBTextBuffer,
@@ -1284,5 +1545,6 @@ export {
1284
1545
  createMindooDBRichTextHandle,
1285
1546
  createMindooDBTextBuffer,
1286
1547
  createViewLanguage,
1287
- expandAbbreviatedName
1548
+ expandAbbreviatedName,
1549
+ queryDocuments
1288
1550
  };
@@ -1,4 +1,4 @@
1
- import { ae as MindooDBAppLaunchContext, Z as MindooDBAppDatabaseInfo, _ as MindooDBAppDocumentApi, Y as MindooDBAppCreateViewNavigatorInput, aT as MindooDBAppViewNavigator, aV as MindooDBAppViewNavigatorOpenOptions, f as MindooDBAppAttachmentApi, a as MindooDBAppBridge, aH as MindooDBAppSession, s as MindooDBAppBridgeRpcRequest, a8 as MindooDBAppHostTheme, a_ as MindooDBAppViewport, aN as MindooDBAppUiPreferences, p as MindooDBAppBridgePortMessage } from '../types-nFt-aryL.js';
1
+ import { au as MindooDBAppLaunchContext, a7 as MindooDBAppDatabaseInfo, a8 as MindooDBAppDocumentApi, a6 as MindooDBAppCreateViewNavigatorInput, bh as MindooDBAppViewNavigator, bj as MindooDBAppViewNavigatorOpenOptions, h as MindooDBAppAttachmentApi, ai as MindooDBAppFulltextSetup, ah as MindooDBAppExtractionSetup, a as MindooDBAppBridge, b5 as MindooDBAppSession, B as MindooDBAppBridgeRpcRequest, al as MindooDBAppHostTheme, bp as MindooDBAppViewport, bb as MindooDBAppUiPreferences, aK as MindooDBAppQueryResult, bo as MindooDBAppViewUpdateStats, x as MindooDBAppBridgePortMessage } from '../types-B6vEy1VC.js';
2
2
  import 'mindoodb-view-language';
3
3
 
4
4
  type MaybePromise<T> = T | Promise<T>;
@@ -14,6 +14,21 @@ type MockDatabaseMethods = {
14
14
  interface MockMindooDBAppDatabaseDefinition {
15
15
  info: MindooDBAppDatabaseInfo;
16
16
  methods?: MockDatabaseMethods;
17
+ /**
18
+ * Initial full-text configuration returned by `getFulltextSetup()`.
19
+ * `setFulltextSetup()` overwrites it for the lifetime of the handle.
20
+ * Note the mock evaluates `text` query clauses regardless of this
21
+ * config (see TESTING.md) — it exists so apps can test their setup/
22
+ * bootstrap logic.
23
+ */
24
+ fulltextSetup?: MindooDBAppFulltextSetup | null;
25
+ /**
26
+ * Initial extraction configuration returned by `getExtractionSetup()`.
27
+ * `setExtractionSetup()` overwrites it for the lifetime of the handle.
28
+ * The mock runs no extraction service — this exists so apps can test
29
+ * their setup/bootstrap logic.
30
+ */
31
+ extractionSetup?: MindooDBAppExtractionSetup | null;
17
32
  }
18
33
  interface CreateMockMindooDBAppSessionOptions {
19
34
  launchContext?: Partial<MindooDBAppLaunchContext>;
@@ -25,11 +40,13 @@ interface MockMindooDBAppSessionController {
25
40
  session: MindooDBAppSession;
26
41
  getLaunchContext(): MindooDBAppLaunchContext;
27
42
  setLaunchContext(patch?: Partial<MindooDBAppLaunchContext>): MindooDBAppLaunchContext;
43
+ getLicensedProducts(): string[];
28
44
  listDatabases(): MindooDBAppDatabaseInfo[];
29
45
  setDatabases(definitions: MockMindooDBAppDatabaseDefinition[]): void;
30
46
  emitThemeChange(theme: MindooDBAppHostTheme): void;
31
47
  emitViewportChange(viewport: MindooDBAppViewport): void;
32
48
  emitUiPreferencesChange(uiPreferences: MindooDBAppUiPreferences): void;
49
+ emitLocaleChange(locale: string): void;
33
50
  }
34
51
  declare function createMockMindooDBAppSession(options?: CreateMockMindooDBAppSessionOptions): MockMindooDBAppSessionController;
35
52
  declare function createMockMindooDBAppBridge(options?: CreateMockMindooDBAppSessionOptions): MockMindooDBAppSessionController;
@@ -57,6 +74,11 @@ interface FakeBridgeHostController {
57
74
  emitThemeChange(theme: MindooDBAppHostTheme): void;
58
75
  emitViewportChange(viewport: MindooDBAppViewport): void;
59
76
  emitUiPreferencesChange(uiPreferences: MindooDBAppUiPreferences): void;
77
+ emitLocaleChange(locale: string): void;
78
+ /** Push a live-query result to connected apps, as the Haven host would. */
79
+ emitQueryResult(subscriptionId: string, result: MindooDBAppQueryResult): void;
80
+ /** Push a navigator view-changed event to connected apps. */
81
+ emitViewChanged(navigatorId: string, stats: MindooDBAppViewUpdateStats): void;
60
82
  postPortMessage(message: MindooDBAppBridgePortMessage, transfer?: Transferable[]): void;
61
83
  setRequestHandler(method: string, handler: FakeBridgeRequestHandler): void;
62
84
  clearRequestHandler(method: string): void;