mindoodb-app-sdk 0.0.46 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/TESTING.md +35 -0
- package/dist/{chunk-XPPTUWW6.js → chunk-TPYDERAK.js} +25 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +268 -2
- package/dist/testing/index.d.ts +10 -1
- package/dist/testing/index.js +395 -42
- package/dist/{types-BaXvRKXY.d.ts → types-C-eyC839.d.ts} +274 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1084,6 +1084,43 @@ if (handled) {
|
|
|
1084
1084
|
}
|
|
1085
1085
|
```
|
|
1086
1086
|
|
|
1087
|
+
**Nested lookups (`include`)** fetch related documents alongside the matching rows — from the same database or from any other database the app is mapped to. The host resolves a slot with **one scan per slot**, not one query per row, so a list with its related records is a single round trip:
|
|
1088
|
+
|
|
1089
|
+
```ts
|
|
1090
|
+
const result = await db.documents.query<{
|
|
1091
|
+
customer: MindooDBAppQueryRow | null;
|
|
1092
|
+
lines: MindooDBAppQueryRow[];
|
|
1093
|
+
}>({
|
|
1094
|
+
filter: 'v.eq(v.field("type"), "invoice")',
|
|
1095
|
+
fields: ["total", "customerId"],
|
|
1096
|
+
include: {
|
|
1097
|
+
customer: {
|
|
1098
|
+
databaseId: "customers", // another mapped database; omit for this one
|
|
1099
|
+
cardinality: "one", // → a row or null
|
|
1100
|
+
localKey: "customerId", // this row's field holds the customer's id
|
|
1101
|
+
fields: ["name"],
|
|
1102
|
+
},
|
|
1103
|
+
lines: {
|
|
1104
|
+
cardinality: "many", // → an array (possibly empty, never null)
|
|
1105
|
+
filter: 'v.eq(v.field("invoiceId"), v.parentDocId())',
|
|
1106
|
+
sortBy: [{ field: "position", direction: "ascending" }],
|
|
1107
|
+
limit: 50,
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
for (const row of result.rows) {
|
|
1113
|
+
console.log(row.fields.total, row.includes?.customer?.fields.name);
|
|
1114
|
+
console.log(row.includes?.lines.length);
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
- Related documents live under `row.includes[slot]`, never as sibling keys, so a slot name can never collide with `docId` / `fields` / `lastModified`. Pass the slot types to `query<TIncludes>()` to have them typed.
|
|
1119
|
+
- The join condition relates the related document to the parent row: `v.parentDocId()` for the parent's id, `v.parent("<path>")` for one of its fields. `localKey` is shorthand for "this row's field holds the related document's id" (an array value joins every element). Exactly one such equality is allowed per slot — further conditions may narrow the related documents but must not mention the parent.
|
|
1120
|
+
- Slots nest (`row.includes.customer.includes.address`) up to three levels.
|
|
1121
|
+
- `databaseId` is checked like any other database parameter: the app must be mapped to it with the `read` capability, at every level. Unmapped or view-only ids are rejected with `database-not-found`, a missing capability with `forbidden`.
|
|
1122
|
+
- Hydration happens after sorting and paging, so only the returned rows are joined and `total` stays the unpaged match count. Per-slot `limit` is capped at 1000, and a query may carry at most 10 slots in total.
|
|
1123
|
+
|
|
1087
1124
|
**Live queries** keep a query result up to date. The callback receives the initial result and runs again whenever the result actually changed — the host coalesces bursts of writes and fingerprints results, so you only hear about real changes:
|
|
1088
1125
|
|
|
1089
1126
|
```ts
|
|
@@ -1101,7 +1138,7 @@ await subscription.refresh();
|
|
|
1101
1138
|
await subscription.dispose();
|
|
1102
1139
|
```
|
|
1103
1140
|
|
|
1104
|
-
Live query results are pushed from the host over the bridge port — no polling. Subscriptions are cleaned up automatically when the app disconnects, but dispose them explicitly when the UI that consumes them unmounts.
|
|
1141
|
+
Live query results are pushed from the host over the bridge port — no polling. Subscriptions are cleaned up automatically when the app disconnects, but dispose them explicitly when the UI that consumes them unmounts. A live query with `include` also watches every related database, so a change to a joined document delivers a new result too.
|
|
1105
1142
|
|
|
1106
1143
|
### Incremental sync
|
|
1107
1144
|
|
|
@@ -1638,8 +1675,8 @@ Host-kept key/value state, the only durable storage a hosted app has. See [Stora
|
|
|
1638
1675
|
|
|
1639
1676
|
| Method | Returns |
|
|
1640
1677
|
| ---------------------------------- | -------------------------------------------- |
|
|
1641
|
-
| `query(query?)`
|
|
1642
|
-
| `liveQuery(query, onResult)`
|
|
1678
|
+
| `query<TIncludes>(query?)` | `Promise<MindooDBAppQueryResult<TIncludes>>` |
|
|
1679
|
+
| `liveQuery<TIncludes>(query, onResult)` | `Promise<MindooDBAppLiveQuerySubscription>` |
|
|
1643
1680
|
| `list(query?)` | `Promise<MindooDBAppDocumentListResult>` |
|
|
1644
1681
|
| `listInaccessible(query?)` | `Promise<MindooDBAppInaccessibleDocumentListResult>` |
|
|
1645
1682
|
| `get(docId)` | `Promise<MindooDBAppDocument \| null>` |
|
package/TESTING.md
CHANGED
|
@@ -183,6 +183,41 @@ await db.documents.create({ set: { type: "invoice", total: 50 } });
|
|
|
183
183
|
await sub.dispose();
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
Nested lookups (`include`) work too, including slots that name another mocked
|
|
187
|
+
database by its id, and they mirror the host's rules — one parent equality per
|
|
188
|
+
slot, `cardinality: "one"` failing loudly when several documents match, per-slot
|
|
189
|
+
`sortBy`/`limit`, and nesting. A write to a joined database re-fires the live
|
|
190
|
+
queries that join it, so cross-database joins can be tested end to end:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const mock = createMockMindooDBAppBridge({
|
|
194
|
+
databases: [
|
|
195
|
+
{
|
|
196
|
+
info: { id: "billing", title: "Billing", capabilities: ["read", "create"] },
|
|
197
|
+
documents: [{ id: "inv_1", data: { type: "invoice", customerId: "cust_1" } }],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
info: { id: "customers", title: "Customers", capabilities: ["read"] },
|
|
201
|
+
documents: [{ id: "cust_1", data: { name: "Acme" } }],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const session = await mock.bridge.connect();
|
|
207
|
+
const billing = await session.openDatabase("billing");
|
|
208
|
+
|
|
209
|
+
const result = await billing.documents.query<{ customer: MindooDBAppQueryRow | null }>({
|
|
210
|
+
filter: 'v.eq(v.field("type"), "invoice")',
|
|
211
|
+
include: {
|
|
212
|
+
customer: { databaseId: "customers", cardinality: "one", localKey: "customerId" },
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
expect(result.rows[0].includes?.customer?.fields.name).toBe("Acme");
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
An include slot naming a database that was not seeded fails, the same way Haven
|
|
219
|
+
rejects a database the app is not mapped to.
|
|
220
|
+
|
|
186
221
|
## Level 2 example
|
|
187
222
|
|
|
188
223
|
This pattern keeps the real `createMindooDBAppBridge()` code path and replaces only the host side.
|
|
@@ -658,14 +658,28 @@ function normalizeDocumentQuery(query) {
|
|
|
658
658
|
if (!query) {
|
|
659
659
|
return {};
|
|
660
660
|
}
|
|
661
|
-
const { filter, ...rest } = query;
|
|
661
|
+
const { filter, include, ...rest } = query;
|
|
662
662
|
return {
|
|
663
663
|
...rest,
|
|
664
|
-
...filter !== void 0 ? {
|
|
665
|
-
|
|
666
|
-
} : {}
|
|
664
|
+
...filter !== void 0 ? { filter: normalizeQueryFilter(filter) } : {},
|
|
665
|
+
...include !== void 0 ? { include: normalizeQueryIncludes(include) } : {}
|
|
667
666
|
};
|
|
668
667
|
}
|
|
668
|
+
function normalizeQueryFilter(filter) {
|
|
669
|
+
return typeof filter === "string" ? parseMindooDBFormulaBooleanExpression(filter) : filter;
|
|
670
|
+
}
|
|
671
|
+
function normalizeQueryIncludes(includes) {
|
|
672
|
+
const normalized = {};
|
|
673
|
+
for (const [slot, include] of Object.entries(includes)) {
|
|
674
|
+
const { filter, include: nested, ...rest } = include;
|
|
675
|
+
normalized[slot] = {
|
|
676
|
+
...rest,
|
|
677
|
+
...filter !== void 0 ? { filter: normalizeQueryFilter(filter) } : {},
|
|
678
|
+
...nested !== void 0 ? { include: normalizeQueryIncludes(nested) } : {}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
return normalized;
|
|
682
|
+
}
|
|
669
683
|
function isThemeChangedMessage(message) {
|
|
670
684
|
return message.kind === "theme-changed";
|
|
671
685
|
}
|
|
@@ -1766,6 +1780,13 @@ var MindooDBAppSessionImpl = class {
|
|
|
1766
1780
|
async getLicensedProducts() {
|
|
1767
1781
|
return await this.rpc.call("session.getLicensedProducts", {});
|
|
1768
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Asks the host to install another app. Only a URL crosses the bridge: the host
|
|
1785
|
+
* reads that origin's `haven-app.json` itself and asks the user.
|
|
1786
|
+
*/
|
|
1787
|
+
async proposeApp(input) {
|
|
1788
|
+
return await this.rpc.call("apps.propose", input);
|
|
1789
|
+
}
|
|
1769
1790
|
/** Lists the databases that the host has made available for this app session. */
|
|
1770
1791
|
async listDatabases() {
|
|
1771
1792
|
return await this.rpc.call("session.listDatabases", {});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppDragBindSourceOptions, b as MindooDBAppDragStartInput, c as MindooDBAppDragStartResult, d as MindooDBAppDragPreview, e as MindooDBAppBridge, f as MindooDBAppHostShortcutBinding, g as MindooDBAppHostShortcutAction, h as MindooDBAppHostingMode, i as MindooDBAppDatabase, j as MindooDBAppDocument, k as MindooDBAppRichTextSpan, l as MindooDBAppRichTextSnapshot } from './types-
|
|
2
|
-
export { m as
|
|
1
|
+
import { M as MindooDBAppAttachmentPreviewMode, a as MindooDBAppDragBindSourceOptions, b as MindooDBAppDragStartInput, c as MindooDBAppDragStartResult, d as MindooDBAppDragPreview, e as MindooDBAppBridge, f as MindooDBAppHostShortcutBinding, g as MindooDBAppHostShortcutAction, h as MindooDBAppHostingMode, i as MindooDBAppDatabase, j as MindooDBAppDocument, k as MindooDBAppRichTextSpan, l as MindooDBAppRichTextSnapshot } from './types-C-eyC839.js';
|
|
2
|
+
export { m as MINDOODB_APP_DEFINITION_FILE_NAME, n as MINDOODB_APP_DEFINITION_FORMAT, o as MINDOODB_APP_DEFINITION_PERMISSIONS, p as MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS, q as MINDOODB_APP_DEFINITION_VERSION, r as MINDOODB_APP_SEAL_SIGNING_DOMAIN, s as MINDOODB_APP_WELL_KNOWN_DRAG_TYPES, t as MindooDBAppAccessDecision, u as MindooDBAppAccessTier, v as MindooDBAppAttachmentApi, w as MindooDBAppAttachmentChunk, x as MindooDBAppAttachmentInfo, y as MindooDBAppAttachmentPreviewOptions, z as MindooDBAppAttachmentPreviewSession, A as MindooDBAppAutomergeChangesPatch, B as MindooDBAppAutomergeChangesSince, C as MindooDBAppAutomergeGetOptions, D as MindooDBAppAutomergePatchResult, E as MindooDBAppAutomergeSnapshot, F as MindooDBAppBridgeBeforeCloseAck, G as MindooDBAppBridgeBeforeCloseMessage, H as MindooDBAppBridgeConnectMessage, I as MindooDBAppBridgeConnectOptions, J as MindooDBAppBridgeConnectedMessage, K as MindooDBAppBridgeDragDropMessage, L as MindooDBAppBridgeDragLeaveMessage, N as MindooDBAppBridgeDragOverMessage, O as MindooDBAppBridgeErrorPayload, P as MindooDBAppBridgeHandshakeErrorMessage, Q as MindooDBAppBridgeLocaleChangedMessage, R as MindooDBAppBridgePortMessage, S as MindooDBAppBridgeQueryResultMessage, T as MindooDBAppBridgeRpcError, U as MindooDBAppBridgeRpcMessage, V as MindooDBAppBridgeRpcRequest, W as MindooDBAppBridgeRpcSuccess, X as MindooDBAppBridgeShortcutInvokedMessage, Y as MindooDBAppBridgeShortcutsChangedMessage, Z as MindooDBAppBridgeStreamAbortRequest, _ as MindooDBAppBridgeStreamAck, $ as MindooDBAppBridgeStreamCloseRequest, a0 as MindooDBAppBridgeStreamError, a1 as MindooDBAppBridgeStreamMessage, a2 as MindooDBAppBridgeStreamOpenResult, a3 as MindooDBAppBridgeStreamReadRequest, a4 as MindooDBAppBridgeStreamWriteRequest, a5 as MindooDBAppBridgeThemeChangedMessage, a6 as MindooDBAppBridgeUiPreferencesChangedMessage, a7 as MindooDBAppBridgeViewChangedMessage, a8 as MindooDBAppBridgeViewportChangedMessage, a9 as MindooDBAppCapability, aa as MindooDBAppConfiguredViewCategorizationStyle, ab as MindooDBAppConfiguredViewColumn, ac as MindooDBAppConfiguredViewColumnRole, ad as MindooDBAppConfiguredViewExpressionMode, ae as MindooDBAppConfiguredViewFieldExpression, af as MindooDBAppConfiguredViewFilterDefinition, ag as MindooDBAppConfiguredViewFilterMode, ah as MindooDBAppConfiguredViewFilterRule, ai as MindooDBAppConfiguredViewFormulaExpression, aj as MindooDBAppConfiguredViewFormulaFilterDefinition, ak as MindooDBAppConfiguredViewPreviewMode, al as MindooDBAppConfiguredViewRuleFilterDefinition, am as MindooDBAppConfiguredViewRuleMatchMode, an as MindooDBAppConfiguredViewRuleOperator, ao as MindooDBAppConfiguredViewValueExpression, ap as MindooDBAppCreateDocumentInput, aq as MindooDBAppCreateKeyInfo, ar as MindooDBAppCreateViewInput, as as MindooDBAppCreateViewNavigatorInput, at as MindooDBAppDatabaseInfo, au as MindooDBAppDefinition, av as MindooDBAppDefinitionDatabase, aw as MindooDBAppDefinitionPermission, ax as MindooDBAppDefinitionRegistrationPermission, ay as MindooDBAppDefinitionValidation, az as MindooDBAppDirectoryApi, aA as MindooDBAppDirectoryEntry, aB as MindooDBAppDocumentApi, aC as MindooDBAppDocumentHeadCursorResult, aD as MindooDBAppDocumentHistoryEntry, aE as MindooDBAppDocumentListQuery, aF as MindooDBAppDocumentListResult, aG as MindooDBAppDocumentQuery, aH as MindooDBAppDocumentRevisionId, aI as MindooDBAppDocumentScanPreset, aJ as MindooDBAppDocumentSummary, aK as MindooDBAppDragApi, aL as MindooDBAppDragCancelReason, aM as MindooDBAppDragDropEvent, aN as MindooDBAppDragEffect, aO as MindooDBAppDragHoverResult, aP as MindooDBAppDragOffer, aQ as MindooDBAppDragOverEvent, aR as MindooDBAppDragPointer, aS as MindooDBAppDragProfile, aT as MindooDBAppEntryAttachmentRef, aU as MindooDBAppEntryProvenance, aV as MindooDBAppExtractDocumentTextInput, aW as MindooDBAppExtractDocumentTextResult, aX as MindooDBAppExtractTextFormat, aY as MindooDBAppExtractionSetup, aZ as MindooDBAppFulltextSetup, a_ as MindooDBAppHistoricalDocument, a$ as MindooDBAppHistoryQuery, b0 as MindooDBAppHostTheme, b1 as MindooDBAppIdentityApi, b2 as MindooDBAppInaccessibleDocument, b3 as MindooDBAppInaccessibleDocumentListQuery, b4 as MindooDBAppInaccessibleDocumentListResult, b5 as MindooDBAppJsonListDeletePatch, b6 as MindooDBAppJsonListInsertPatch, b7 as MindooDBAppJsonPatch, b8 as MindooDBAppJsonSetPatch, b9 as MindooDBAppJsonTextMarkPatch, ba as MindooDBAppJsonTextSplicePatch, bb as MindooDBAppJsonTextUnmarkPatch, bc as MindooDBAppJsonUnsetPatch, bd as MindooDBAppLaunchContext, be as MindooDBAppLiveQuerySubscription, bf as MindooDBAppMenuAnchor, bg as MindooDBAppMenuApi, bh as MindooDBAppMenuCommandItem, bi as MindooDBAppMenuDismissReason, bj as MindooDBAppMenuDismissedResult, bk as MindooDBAppMenuItem, bl as MindooDBAppMenuKind, bm as MindooDBAppMenuPlacement, bn as MindooDBAppMenuPointAnchor, bo as MindooDBAppMenuRect, bp as MindooDBAppMenuRectAnchor, bq as MindooDBAppMenuSelectionResult, br as MindooDBAppMenuSeparatorItem, bs as MindooDBAppProposeAppInput, bt as MindooDBAppProposeAppResult, bu as MindooDBAppQueryCoverage, bv as MindooDBAppQueryInclude, bw as MindooDBAppQueryIncludeCardinality, bx as MindooDBAppQueryIncludeSlots, by as MindooDBAppQueryResult, bz as MindooDBAppQueryRow, bA as MindooDBAppQuerySortKey, bB as MindooDBAppQueryTextClause, bC as MindooDBAppReadableAttachmentStream, bD as MindooDBAppRecipientOptions, bE as MindooDBAppResolvedViewDefinition, bF as MindooDBAppResolvedViewSource, bG as MindooDBAppRevisionPhase, bH as MindooDBAppRevisionVerification, bI as MindooDBAppRichTextBlockSpan, bJ as MindooDBAppRichTextGetOptions, bK as MindooDBAppRichTextImmutableString, bL as MindooDBAppRichTextMarkRange, bM as MindooDBAppRichTextMaterializeValue, bN as MindooDBAppRichTextPatch, bO as MindooDBAppRichTextScalar, bP as MindooDBAppRichTextSpliceStep, bQ as MindooDBAppRichTextStep, bR as MindooDBAppRichTextStepPatch, bS as MindooDBAppRichTextTextSpan, bT as MindooDBAppRuntime, bU as MindooDBAppScanAttachmentOptions, bV as MindooDBAppScanAttachmentResult, bW as MindooDBAppScopedDocId, bX as MindooDBAppScopedDocumentSummary, bY as MindooDBAppSealIntent, bZ as MindooDBAppSealedChannelOpenInput, b_ as MindooDBAppSealedChannelOpenResult, b$ as MindooDBAppSealedChannelRequestInput, c0 as MindooDBAppSession, c1 as MindooDBAppShowMenuInput, c2 as MindooDBAppShowMenuResult, c3 as MindooDBAppSignPreview, c4 as MindooDBAppSignPreviewScope, c5 as MindooDBAppSignStatementInput, c6 as MindooDBAppSignStatementResult, c7 as MindooDBAppStorageApi, c8 as MindooDBAppTextEdit, c9 as MindooDBAppTextPatch, ca as MindooDBAppThemeMode, cb as MindooDBAppTimestampApi, cc as MindooDBAppTimestampInput, cd as MindooDBAppTimestampProvider, ce as MindooDBAppTimestampResult, cf as MindooDBAppUiPreferences, cg as MindooDBAppUpdateDocumentInput, ch as MindooDBAppViewCursorDocumentListResult, ci as MindooDBAppViewCursorResult, cj as MindooDBAppViewEntry, ck as MindooDBAppViewEntryKind, cl as MindooDBAppViewNavigator, cm as MindooDBAppViewNavigatorExpansionState, cn as MindooDBAppViewNavigatorOpenOptions, co as MindooDBAppViewNavigatorPageOptions, cp as MindooDBAppViewNavigatorPageResult, cq as MindooDBAppViewNavigatorRangeQuery, cr as MindooDBAppViewNavigatorSelectionState, cs as MindooDBAppViewUpdateStats, ct as MindooDBAppViewport, cu as MindooDBAppWellKnownDragType, cv as MindooDBAppWritableAttachmentStream, cw as resolveMindooDBAppDefinitionUrl, cx as validateMindooDBAppDefinition } from './types-C-eyC839.js';
|
|
3
3
|
export { M as MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME, a as MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME, b as MINDOODB_APP_BUNDLE_MANIFEST_FORMAT, c as MINDOODB_APP_BUNDLE_MANIFEST_VERSION, d as MindooDBAppBundleArchive, e as MindooDBAppBundleFileEntry, f as MindooDBAppBundleHash, g as MindooDBAppBundleManifest, h as MindooDBAppBundleManifestValidation, i as buildMindooDBAppBundleContentHashInput, j as isMindooDBAppBundleHash, n as normalizeMindooDBAppBundlePath, v as validateMindooDBAppBundleManifest } from './appBundleManifest-B0xfpaXG.js';
|
|
4
4
|
import { MindooDBAppBooleanExpression } from 'mindoodb-view-language';
|
|
5
5
|
export { MindooDBAppBooleanExpression, MindooDBAppExpression, MindooDBAppFilterDefinition, MindooDBAppViewCategoryChildrenPageRequest, MindooDBAppViewColumn, MindooDBAppViewColumnRole, MindooDBAppViewDefinition, MindooDBAppViewExpansionState, MindooDBAppViewExpressionBase, MindooDBAppViewExpressionDatePart, MindooDBAppViewExpressionOperation, MindooDBAppViewFieldExpression, MindooDBAppViewIfExpression, MindooDBAppViewLetExpression, MindooDBAppViewLiteralExpression, MindooDBAppViewLookupByPath, MindooDBAppViewOperationExpression, MindooDBAppViewOriginExpression, MindooDBAppViewPageRequest, MindooDBAppViewPageResult, MindooDBAppViewRow, MindooDBAppViewSortDirection, MindooDBAppViewTotalMode, MindooDBAppViewValueExpressionRef, MindooDBAppViewVariableExpression, createViewLanguage } from 'mindoodb-view-language';
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
releaseMindooDBAppBridgeSessions,
|
|
22
22
|
sanitizeHostShortcutBindings,
|
|
23
23
|
snapshotElementToPng
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-TPYDERAK.js";
|
|
25
25
|
import {
|
|
26
26
|
MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME,
|
|
27
27
|
MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME,
|
|
@@ -42,6 +42,265 @@ var MINDOODB_APP_WELL_KNOWN_DRAG_TYPES = [
|
|
|
42
42
|
];
|
|
43
43
|
var MINDOODB_APP_SEAL_SIGNING_DOMAIN = "mindoodb-seal/v1";
|
|
44
44
|
|
|
45
|
+
// src/appDefinition.ts
|
|
46
|
+
var MINDOODB_APP_DEFINITION_FORMAT = "mindoodb.haven.app";
|
|
47
|
+
var MINDOODB_APP_DEFINITION_VERSION = 1;
|
|
48
|
+
var MINDOODB_APP_DEFINITION_FILE_NAME = "haven-app.json";
|
|
49
|
+
var MINDOODB_APP_DEFINITION_PERMISSIONS = [
|
|
50
|
+
"write",
|
|
51
|
+
"delete",
|
|
52
|
+
"history",
|
|
53
|
+
"attachments",
|
|
54
|
+
"views",
|
|
55
|
+
"sign",
|
|
56
|
+
"timestamps",
|
|
57
|
+
"directory",
|
|
58
|
+
"sealedchannel",
|
|
59
|
+
"proposeapps"
|
|
60
|
+
];
|
|
61
|
+
var MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS = ["proposeapps"];
|
|
62
|
+
var LOGICAL_DATABASE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
63
|
+
function isPlainObject(value) {
|
|
64
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
function readOptionalString(raw, key, errors) {
|
|
67
|
+
const value = raw[key];
|
|
68
|
+
if (value === void 0 || value === null) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value !== "string") {
|
|
72
|
+
errors.push(`App definition ${key} must be a string.`);
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
const trimmed = value.trim();
|
|
76
|
+
return trimmed || void 0;
|
|
77
|
+
}
|
|
78
|
+
function readOptionalBoolean(raw, key, errors) {
|
|
79
|
+
const value = raw[key];
|
|
80
|
+
if (value === void 0 || value === null) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
if (typeof value !== "boolean") {
|
|
84
|
+
errors.push(`App definition ${key} must be a boolean.`);
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
return value || void 0;
|
|
88
|
+
}
|
|
89
|
+
function readStringRecord(raw, key, errors) {
|
|
90
|
+
const value = raw[key];
|
|
91
|
+
if (value === void 0 || value === null) {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
if (!isPlainObject(value)) {
|
|
95
|
+
errors.push(`App definition ${key} must be an object of string values.`);
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
100
|
+
if (typeof entryValue !== "string") {
|
|
101
|
+
errors.push(`App definition ${key}.${entryKey} must be a string.`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
result[entryKey] = entryValue;
|
|
105
|
+
}
|
|
106
|
+
return Object.keys(result).length ? result : void 0;
|
|
107
|
+
}
|
|
108
|
+
function readPermissions(value, label, allowed, errors) {
|
|
109
|
+
if (value === void 0 || value === null) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(value)) {
|
|
113
|
+
errors.push(`${label} must be an array of permission names.`);
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const entry of value) {
|
|
118
|
+
if (typeof entry !== "string" || !allowed.includes(entry)) {
|
|
119
|
+
errors.push(`${label} contains the unknown permission ${JSON.stringify(entry)}.`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!result.includes(entry)) {
|
|
123
|
+
result.push(entry);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result.length ? result : void 0;
|
|
127
|
+
}
|
|
128
|
+
function readNetworkAllowlist(value, errors) {
|
|
129
|
+
if (value === void 0 || value === null) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
if (!Array.isArray(value)) {
|
|
133
|
+
errors.push("App definition networkAllowlist must be an array of origins.");
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
const result = [];
|
|
137
|
+
for (const entry of value) {
|
|
138
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
139
|
+
errors.push(`App definition networkAllowlist contains the invalid entry ${JSON.stringify(entry)}.`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const trimmed = entry.trim();
|
|
143
|
+
if (!result.includes(trimmed)) {
|
|
144
|
+
result.push(trimmed);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result.length ? result : void 0;
|
|
148
|
+
}
|
|
149
|
+
function readDatabases(value, errors) {
|
|
150
|
+
if (value === void 0 || value === null) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(value)) {
|
|
154
|
+
errors.push("App definition databases must be an array.");
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
const result = [];
|
|
158
|
+
const seen = /* @__PURE__ */ new Set();
|
|
159
|
+
value.forEach((entry, index) => {
|
|
160
|
+
if (!isPlainObject(entry)) {
|
|
161
|
+
errors.push(`App definition database #${index} must be an object.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const logicalDatabaseId = typeof entry.logicalDatabaseId === "string" ? entry.logicalDatabaseId.trim() : "";
|
|
165
|
+
if (!logicalDatabaseId) {
|
|
166
|
+
errors.push(`App definition database #${index} is missing a non-empty logicalDatabaseId.`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!LOGICAL_DATABASE_ID_PATTERN.test(logicalDatabaseId)) {
|
|
170
|
+
errors.push(
|
|
171
|
+
`App definition database "${logicalDatabaseId}" must match ${String(LOGICAL_DATABASE_ID_PATTERN)}.`
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (seen.has(logicalDatabaseId)) {
|
|
176
|
+
errors.push(`App definition lists the database "${logicalDatabaseId}" more than once.`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
seen.add(logicalDatabaseId);
|
|
180
|
+
const databaseId = typeof entry.databaseId === "string" ? entry.databaseId.trim() : "";
|
|
181
|
+
if (entry.databaseId !== void 0 && entry.databaseId !== null && !databaseId) {
|
|
182
|
+
errors.push(`App definition database "${logicalDatabaseId}" has an empty databaseId.`);
|
|
183
|
+
}
|
|
184
|
+
result.push({
|
|
185
|
+
logicalDatabaseId,
|
|
186
|
+
label: typeof entry.label === "string" && entry.label.trim() ? entry.label.trim() : void 0,
|
|
187
|
+
databaseId: databaseId || void 0,
|
|
188
|
+
permissions: readPermissions(
|
|
189
|
+
entry.permissions,
|
|
190
|
+
`App definition database "${logicalDatabaseId}" permissions`,
|
|
191
|
+
MINDOODB_APP_DEFINITION_PERMISSIONS.filter(
|
|
192
|
+
(permission) => !MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS.includes(permission)
|
|
193
|
+
),
|
|
194
|
+
errors
|
|
195
|
+
),
|
|
196
|
+
create: entry.create === false ? false : void 0
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
return result.length ? result : void 0;
|
|
200
|
+
}
|
|
201
|
+
function validateMindooDBAppDefinition(raw) {
|
|
202
|
+
const errors = [];
|
|
203
|
+
if (!isPlainObject(raw)) {
|
|
204
|
+
return { definition: null, errors: ["App definition must be a JSON object."] };
|
|
205
|
+
}
|
|
206
|
+
if (raw.format !== MINDOODB_APP_DEFINITION_FORMAT) {
|
|
207
|
+
errors.push(
|
|
208
|
+
`App definition format must be "${MINDOODB_APP_DEFINITION_FORMAT}", received ${JSON.stringify(raw.format)}.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (raw.formatVersion !== MINDOODB_APP_DEFINITION_VERSION) {
|
|
212
|
+
errors.push(
|
|
213
|
+
`Unsupported app definition version ${JSON.stringify(raw.formatVersion)}, expected ${MINDOODB_APP_DEFINITION_VERSION}.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const appId = typeof raw.appId === "string" ? raw.appId.trim() : "";
|
|
217
|
+
if (!appId) {
|
|
218
|
+
errors.push("App definition is missing a non-empty appId.");
|
|
219
|
+
}
|
|
220
|
+
const label = typeof raw.label === "string" ? raw.label.trim() : "";
|
|
221
|
+
if (!label) {
|
|
222
|
+
errors.push("App definition is missing a non-empty label.");
|
|
223
|
+
}
|
|
224
|
+
const runtime = raw.runtime;
|
|
225
|
+
if (runtime !== void 0 && runtime !== "iframe" && runtime !== "window") {
|
|
226
|
+
errors.push(`App definition runtime must be "iframe" or "window", received ${JSON.stringify(runtime)}.`);
|
|
227
|
+
}
|
|
228
|
+
const hosting = raw.hosting;
|
|
229
|
+
if (hosting !== void 0 && hosting !== "external" && hosting !== "hosted") {
|
|
230
|
+
errors.push(
|
|
231
|
+
`App definition hosting must be "external" or "hosted", received ${JSON.stringify(hosting)}.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const description = readOptionalString(raw, "description", errors);
|
|
235
|
+
const version = readOptionalString(raw, "version", errors);
|
|
236
|
+
const defaultLaunchDatabaseId = readOptionalString(raw, "defaultLaunchDatabaseId", errors);
|
|
237
|
+
const networkAllowlist = readNetworkAllowlist(raw.networkAllowlist, errors);
|
|
238
|
+
const launchParameters = readStringRecord(raw, "launchParameters", errors);
|
|
239
|
+
const databases = readDatabases(raw.databases, errors);
|
|
240
|
+
const permissions = readPermissions(
|
|
241
|
+
raw.permissions,
|
|
242
|
+
"App definition permissions",
|
|
243
|
+
MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS,
|
|
244
|
+
errors
|
|
245
|
+
);
|
|
246
|
+
const allowPopups = readOptionalBoolean(raw, "allowPopups", errors);
|
|
247
|
+
const allowCamera = readOptionalBoolean(raw, "allowCamera", errors);
|
|
248
|
+
const allowMicrophone = readOptionalBoolean(raw, "allowMicrophone", errors);
|
|
249
|
+
const allowGeolocation = readOptionalBoolean(raw, "allowGeolocation", errors);
|
|
250
|
+
const allowWebRtc = readOptionalBoolean(raw, "allowWebRtc", errors);
|
|
251
|
+
const allowWorkers = readOptionalBoolean(raw, "allowWorkers", errors);
|
|
252
|
+
if (defaultLaunchDatabaseId && databases && !databases.some((database) => database.logicalDatabaseId === defaultLaunchDatabaseId)) {
|
|
253
|
+
errors.push(
|
|
254
|
+
`App definition defaultLaunchDatabaseId "${defaultLaunchDatabaseId}" is not one of the declared databases.`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (errors.length) {
|
|
258
|
+
return { definition: null, errors };
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
definition: {
|
|
262
|
+
format: MINDOODB_APP_DEFINITION_FORMAT,
|
|
263
|
+
formatVersion: MINDOODB_APP_DEFINITION_VERSION,
|
|
264
|
+
appId,
|
|
265
|
+
label,
|
|
266
|
+
description,
|
|
267
|
+
version,
|
|
268
|
+
runtime,
|
|
269
|
+
hosting,
|
|
270
|
+
networkAllowlist,
|
|
271
|
+
allowPopups,
|
|
272
|
+
allowCamera,
|
|
273
|
+
allowMicrophone,
|
|
274
|
+
allowGeolocation,
|
|
275
|
+
allowWebRtc,
|
|
276
|
+
allowWorkers,
|
|
277
|
+
permissions,
|
|
278
|
+
launchParameters,
|
|
279
|
+
defaultLaunchDatabaseId,
|
|
280
|
+
databases
|
|
281
|
+
},
|
|
282
|
+
errors: []
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function resolveMindooDBAppDefinitionUrl(baseUrl) {
|
|
286
|
+
const trimmed = baseUrl.trim();
|
|
287
|
+
if (!trimmed) {
|
|
288
|
+
return "";
|
|
289
|
+
}
|
|
290
|
+
const suffix = `/${MINDOODB_APP_DEFINITION_FILE_NAME}`;
|
|
291
|
+
try {
|
|
292
|
+
const url = new URL(trimmed);
|
|
293
|
+
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
294
|
+
url.search = "";
|
|
295
|
+
url.hash = "";
|
|
296
|
+
url.pathname = path.endsWith(suffix) || path === suffix ? path : `${path === "/" ? "" : path}${suffix}`;
|
|
297
|
+
return url.toString();
|
|
298
|
+
} catch {
|
|
299
|
+
const withoutSlash = trimmed.replace(/\/+$/, "");
|
|
300
|
+
return withoutSlash.toLowerCase().endsWith(suffix) ? withoutSlash : `${withoutSlash}${suffix}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
45
304
|
// src/attachmentPreview.ts
|
|
46
305
|
function canPreviewAttachment(fileName, mimeType) {
|
|
47
306
|
const normalizedMimeType = mimeType.toLowerCase();
|
|
@@ -780,6 +1039,11 @@ export {
|
|
|
780
1039
|
MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME,
|
|
781
1040
|
MINDOODB_APP_BUNDLE_MANIFEST_FORMAT,
|
|
782
1041
|
MINDOODB_APP_BUNDLE_MANIFEST_VERSION,
|
|
1042
|
+
MINDOODB_APP_DEFINITION_FILE_NAME,
|
|
1043
|
+
MINDOODB_APP_DEFINITION_FORMAT,
|
|
1044
|
+
MINDOODB_APP_DEFINITION_PERMISSIONS,
|
|
1045
|
+
MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS,
|
|
1046
|
+
MINDOODB_APP_DEFINITION_VERSION,
|
|
783
1047
|
MINDOODB_APP_HOSTING_QUERY_PARAM,
|
|
784
1048
|
MINDOODB_APP_MAX_DRAG_PAYLOAD_BYTES,
|
|
785
1049
|
MINDOODB_APP_MAX_DRAG_PREVIEW_BYTES,
|
|
@@ -815,7 +1079,9 @@ export {
|
|
|
815
1079
|
queryDocuments,
|
|
816
1080
|
readMindooDBAppHostingMode,
|
|
817
1081
|
releaseMindooDBAppBridgeSessions,
|
|
1082
|
+
resolveMindooDBAppDefinitionUrl,
|
|
818
1083
|
sanitizeHostShortcutBindings,
|
|
819
1084
|
snapshotElementToPng,
|
|
820
|
-
validateMindooDBAppBundleManifest
|
|
1085
|
+
validateMindooDBAppBundleManifest,
|
|
1086
|
+
validateMindooDBAppDefinition
|
|
821
1087
|
};
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { as as MindooDBAppCreateViewNavigatorInput, cl as MindooDBAppViewNavigator, bd as MindooDBAppLaunchContext, at as MindooDBAppDatabaseInfo, aB as MindooDBAppDocumentApi, cn as MindooDBAppViewNavigatorOpenOptions, v as MindooDBAppAttachmentApi, b1 as MindooDBAppIdentityApi, cb as MindooDBAppTimestampApi, az as MindooDBAppDirectoryApi, j as MindooDBAppDocument, aZ as MindooDBAppFulltextSetup, aY as MindooDBAppExtractionSetup, bs as MindooDBAppProposeAppInput, bt as MindooDBAppProposeAppResult, e as MindooDBAppBridge, c0 as MindooDBAppSession, V as MindooDBAppBridgeRpcRequest, b0 as MindooDBAppHostTheme, ct as MindooDBAppViewport, cf as MindooDBAppUiPreferences, by as MindooDBAppQueryResult, cs as MindooDBAppViewUpdateStats, R as MindooDBAppBridgePortMessage, c as MindooDBAppDragStartResult, aS as MindooDBAppDragProfile } from '../types-C-eyC839.js';
|
|
2
2
|
import * as mindoodb from 'mindoodb';
|
|
3
3
|
import 'mindoodb-view-language';
|
|
4
4
|
|
|
@@ -98,6 +98,11 @@ interface CreateMockMindooDBAppSessionOptions {
|
|
|
98
98
|
databases?: MockMindooDBAppDatabaseDefinition[];
|
|
99
99
|
onDisconnect?: () => MaybePromise<void>;
|
|
100
100
|
sealedChannel?: MockSealedChannelOptions;
|
|
101
|
+
/**
|
|
102
|
+
* Answer for `session.proposeApp`, standing in for the user's decision in the
|
|
103
|
+
* real host's consent dialog. Omitted means declined.
|
|
104
|
+
*/
|
|
105
|
+
proposeApp?: (input: MindooDBAppProposeAppInput) => MindooDBAppProposeAppResult;
|
|
101
106
|
/** Initial contents of `session.storage`. */
|
|
102
107
|
storage?: Record<string, string>;
|
|
103
108
|
}
|
|
@@ -107,6 +112,8 @@ interface MockMindooDBAppSessionController {
|
|
|
107
112
|
getLaunchContext(): MindooDBAppLaunchContext;
|
|
108
113
|
setLaunchContext(patch?: Partial<MindooDBAppLaunchContext>): MindooDBAppLaunchContext;
|
|
109
114
|
getLicensedProducts(): string[];
|
|
115
|
+
/** Every `proposeApp` call the app under test made, in order. */
|
|
116
|
+
proposedApps: MindooDBAppProposeAppInput[];
|
|
110
117
|
listDatabases(): MindooDBAppDatabaseInfo[];
|
|
111
118
|
setDatabases(definitions: MockMindooDBAppDatabaseDefinition[]): void;
|
|
112
119
|
emitThemeChange(theme: MindooDBAppHostTheme): void;
|
|
@@ -140,6 +147,8 @@ interface FakeBridgeHostController {
|
|
|
140
147
|
session: MindooDBAppSession;
|
|
141
148
|
readonly launchId: string;
|
|
142
149
|
readonly requests: ReadonlyArray<MindooDBAppBridgeRpcRequest>;
|
|
150
|
+
/** Every `apps.propose` call the app under test made, in order. */
|
|
151
|
+
readonly proposedApps: ReadonlyArray<MindooDBAppProposeAppInput>;
|
|
143
152
|
install(): void;
|
|
144
153
|
dispose(): void;
|
|
145
154
|
emitThemeChange(theme: MindooDBAppHostTheme): void;
|