mindoodb-app-sdk 0.0.41 → 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/README.md CHANGED
@@ -1472,7 +1472,70 @@ wrangler deploy
1472
1472
 
1473
1473
  Other static hosting options work just as well: Netlify, Vercel, any web server serving your `dist/` folder.
1474
1474
 
1475
- **Haven-hosted bundles** are an alternative: import your built app directly into Haven, where it is served by a service worker on an opaque origin. Haven-hosted apps load without a network connection and receive a stricter sandbox.
1475
+ ### Haven-hosted bundles
1476
+
1477
+ Instead of Haven loading your app from your server on every launch, you can hand Haven the build itself. Haven stores it in its own cache and serves it from a service worker on an opaque origin, so the app loads offline and runs in a stricter sandbox than an external URL gets.
1478
+
1479
+ You can upload a build directory by hand, but the useful form is to publish a **bundle manifest** next to your app and let Haven fetch and update it. Add the plugin to your Vite config:
1480
+
1481
+ ```ts
1482
+ import { havenBundle } from "mindoodb-app-sdk/vite";
1483
+
1484
+ export default defineConfig({
1485
+ // Haven serves the bundle under a path it picks, so assets must be relative.
1486
+ base: "./",
1487
+ plugins: [havenBundle()],
1488
+ });
1489
+ ```
1490
+
1491
+ After `vite build`, `dist/` additionally contains `haven-bundle.json` (app id, version, a SHA-256 for every file, and one for the bundle as a whole) and `haven-bundle.zip` (all of it in one archive). The app's own service worker files are left out — Haven serves the bundle and a second service worker would only fight it for the scope. The user pastes the URL of `haven-bundle.json` into Haven's application settings; Haven downloads the archive, rejects it unless every hash matches the manifest, and installs it.
1492
+
1493
+ Haven then owns updates. It re-reads the manifest before launches and on a slow background timer, and installs a new version when the `contentHash` changes — which means an app in hosted mode should **not** run its own update logic:
1494
+
1495
+ ```ts
1496
+ import { isHostedBundleRuntime } from "mindoodb-app-sdk";
1497
+
1498
+ if (!isHostedBundleRuntime()) {
1499
+ registerServiceWorker();
1500
+ }
1501
+ ```
1502
+
1503
+ Serving the manifest and archive to Haven needs CORS, since Haven is on a different origin than your app:
1504
+
1505
+ ```
1506
+ /haven-bundle.json
1507
+ Access-Control-Allow-Origin: *
1508
+ Cache-Control: no-cache
1509
+
1510
+ /haven-bundle.zip
1511
+ Access-Control-Allow-Origin: *
1512
+ Cache-Control: public, max-age=300
1513
+ ```
1514
+
1515
+ The manifest must not be cached — it is the signal that a new version exists. The archive can be, because the manifest pins it by hash.
1516
+
1517
+ #### Storage in hosted mode
1518
+
1519
+ An opaque origin has no storage: touching `localStorage` throws a `SecurityError`. The host keeps a small amount of key/value state for the app instead, reachable at `session.storage`:
1520
+
1521
+ ```ts
1522
+ await session.storage.set("my-app.theme", "dark");
1523
+ const theme = await session.storage.get("my-app.theme");
1524
+ ```
1525
+
1526
+ That is asynchronous, and most existing app code reads preferences synchronously during render. For those, install a shim before the app boots. It replaces the global `localStorage` with one hydrated from the host, serves reads from memory, and writes keys under the given prefixes back to the host in the background:
1527
+
1528
+ ```ts
1529
+ // entry point, before importing the app
1530
+ await installHavenStorageShim({ persistPrefixes: ["my-app-"] });
1531
+ await import("./main");
1532
+ ```
1533
+
1534
+ Outside hosted mode this does nothing and the real `localStorage` stays in place. Keys outside `persistPrefixes` still work but live only for the session — a deliberate choice, since the budget is meant for preferences.
1535
+
1536
+ Two things are worth knowing when you use it. Writes are coalesced, so a value written on every `pointermove` costs one message rather than sixty per second; still, prefer committing such a value once when the interaction ends. And the budget is finite (256 KB per installed app, 64 KB per value), so `setItem` can throw `QuotaExceededError` just as it can in a browser. Anything larger than a preference belongs in a database.
1537
+
1538
+ Haven asks the app to flush pending writes before it tears the iframe down, so a preference set immediately before closing is not lost. The shim answers that on its own.
1476
1539
 
1477
1540
  ## Local development workflow
1478
1541
 
@@ -1510,10 +1573,25 @@ Connect options: `launchId?`, `targetOrigin?`, `connectTimeoutMs?`.
1510
1573
  | `closeSealedChannel(channelId)` | `Promise<void>` |
1511
1574
  | `menus.show(input)` | `Promise<MindooDBAppShowMenuResult>` |
1512
1575
  | `menus.hide()` | `Promise<void>` |
1576
+ | `storage` | `MindooDBAppStorageApi` |
1513
1577
  | `onThemeChange(listener)` | `() => void` (unsubscribe) |
1514
1578
  | `onViewportChange(listener)` | `() => void` (unsubscribe) |
1579
+ | `onBeforeClose(listener)` | `() => void` (unsubscribe) |
1515
1580
  | `disconnect()` | `Promise<void>` |
1516
1581
 
1582
+ ### MindooDBAppStorageApi
1583
+
1584
+ Host-kept key/value state, the only durable storage a hosted app has. See [Storage in hosted mode](#storage-in-hosted-mode).
1585
+
1586
+ | Method | Returns |
1587
+ | --------------------- | ----------------------------------- |
1588
+ | `snapshot(options?)` | `Promise<Record<string, string>>` |
1589
+ | `get(key)` | `Promise<string \| null>` |
1590
+ | `set(key, value)` | `Promise<void>` |
1591
+ | `remove(key)` | `Promise<void>` |
1592
+ | `keys(options?)` | `Promise<string[]>` |
1593
+ | `clear(options?)` | `Promise<void>` |
1594
+
1517
1595
  ### MindooDBAppDatabase
1518
1596
 
1519
1597
  | Property / Method | Returns |
@@ -1642,6 +1720,14 @@ interface MindooDBAppUpdateDocumentInput {
1642
1720
  | `createViewLanguage<T>()` | Create a typed expression builder for view definitions |
1643
1721
  | `abbreviateCanonicalName(value)` | Convert a canonical Notes-style name like `cn=Jane/ou=Dev/o=Mindoo` to `Jane/Dev/Mindoo` |
1644
1722
  | `expandAbbreviatedName(value)` | Convert an abbreviated Notes-style name like `Jane/Dev/Mindoo` to `cn=Jane/ou=Dev/o=Mindoo` |
1723
+ | `isHostedBundleRuntime(search?)` | Whether Haven is serving this app as a hosted bundle rather than from its own URL |
1724
+ | `installHavenStorageShim(options?)` | Replace `localStorage` with a host-backed shim in hosted mode; a no-op everywhere else |
1725
+
1726
+ The Vite plugin lives behind its own entrypoint:
1727
+
1728
+ | Function | Import from | Description |
1729
+ | ----------------------- | ------------------------- | -------------------------------------------------------- |
1730
+ | `havenBundle(options?)` | `mindoodb-app-sdk/vite` | Emit `haven-bundle.json` and `haven-bundle.zip` on build |
1645
1731
 
1646
1732
  ## Permissions and mappings
1647
1733
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared description of a built application bundle that a MindooDB host can install
3
+ * and update from a URL.
4
+ *
5
+ * The build side (`mindoodb-app-sdk/vite`) writes the manifest, the host side reads and
6
+ * validates it. Both import this module so the format has exactly one definition.
7
+ */
8
+ declare const MINDOODB_APP_BUNDLE_MANIFEST_FORMAT = "mindoodb.app.bundle";
9
+ declare const MINDOODB_APP_BUNDLE_MANIFEST_VERSION = 1;
10
+ declare const MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME = "haven-bundle.json";
11
+ declare const MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME = "haven-bundle.zip";
12
+ /** A `sha256-<64 lowercase hex chars>` digest string. */
13
+ type MindooDBAppBundleHash = string;
14
+ interface MindooDBAppBundleFileEntry {
15
+ /** Bundle-relative POSIX path, e.g. `assets/index-a1b2c3.js`. */
16
+ path: string;
17
+ hash: MindooDBAppBundleHash;
18
+ size: number;
19
+ }
20
+ interface MindooDBAppBundleArchive {
21
+ /** Path of the archive relative to the manifest URL. */
22
+ path: string;
23
+ hash: MindooDBAppBundleHash;
24
+ size: number;
25
+ }
26
+ interface MindooDBAppBundleManifest {
27
+ format: typeof MINDOODB_APP_BUNDLE_MANIFEST_FORMAT;
28
+ formatVersion: number;
29
+ /** Stable identifier of the application, used to detect a swapped bundle source. */
30
+ appId: string;
31
+ /** Human-readable version. Informational only — updates key off `contentHash`. */
32
+ version: string;
33
+ /** Entry HTML file, always present in `files`. */
34
+ entry: string;
35
+ archive: MindooDBAppBundleArchive;
36
+ /** Identity of the bundle: a digest over the sorted file list. */
37
+ contentHash: MindooDBAppBundleHash;
38
+ generatedAt: string;
39
+ files: MindooDBAppBundleFileEntry[];
40
+ }
41
+ declare function isMindooDBAppBundleHash(value: unknown): value is MindooDBAppBundleHash;
42
+ /**
43
+ * Mirrors the host-side path rules: POSIX separators, no leading slash, no `.`/`..`
44
+ * segments and no empty segments. Returns `null` for paths that must be rejected.
45
+ */
46
+ declare function normalizeMindooDBAppBundlePath(rawPath: unknown): string | null;
47
+ /**
48
+ * Canonical byte input for `contentHash`. Both the build and the host derive the digest
49
+ * from this exact string so a mismatch always means the content really differs.
50
+ */
51
+ declare function buildMindooDBAppBundleContentHashInput(files: readonly Pick<MindooDBAppBundleFileEntry, "path" | "hash">[]): string;
52
+ interface MindooDBAppBundleManifestValidation {
53
+ manifest: MindooDBAppBundleManifest | null;
54
+ errors: string[];
55
+ }
56
+ /**
57
+ * Structural validation only — it does not verify any digest. Callers still have to
58
+ * compare `contentHash` and the per-file hashes against the bytes they downloaded.
59
+ */
60
+ declare function validateMindooDBAppBundleManifest(raw: unknown): MindooDBAppBundleManifestValidation;
61
+
62
+ export { MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME as M, MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME as a, MINDOODB_APP_BUNDLE_MANIFEST_FORMAT as b, MINDOODB_APP_BUNDLE_MANIFEST_VERSION as c, type MindooDBAppBundleArchive as d, type MindooDBAppBundleFileEntry as e, type MindooDBAppBundleHash as f, type MindooDBAppBundleManifest as g, type MindooDBAppBundleManifestValidation as h, buildMindooDBAppBundleContentHashInput as i, isMindooDBAppBundleHash as j, normalizeMindooDBAppBundlePath as n, validateMindooDBAppBundleManifest as v };