@bardioc/app-sdk
v0.4.10
Published
SDK for building apps for the Bardioc OS
Readme
@bardioc/app-sdk
SDK and Vite integration for building apps for the Bardioc OS.
This package focuses on the runtime SDK and Vite plugin only:
- Bridge communication between an app and the OS, IndexedDB, and host APIs.
- Validate app manifests and wire Vite apps for iframe-based development.
Requirements
- Node.js
>=20.19.0 - TypeScript
>=6.0.0 - For React apps, install
reactandreact-domin the consuming project
Install into an existing project
pnpm add @bardioc/app-sdkApps can be developed independently outside the Bardioc OS and then tested through a live AppStore development session. That keeps the local development loop fast while still exercising the real host communication path.
Quick start
import { AppSdkProvider, useNotify } from '@bardioc/app-sdk/react';
import { createRoot } from 'react-dom/client';
function App() {
const notify = useNotify();
function sayHello() {
notify('Hello from my app', 'success');
}
return <button onClick={sayHello}>Say hello</button>;
}
const isInsideIframe = globalThis.window.parent !== globalThis.window;
createRoot(document.getElementById('root')!).render(
isInsideIframe ? (
<AppSdkProvider appId="my-app">
<App />
</AppSdkProvider>
) : (
<div>Open this app from the Bardioc OS dock to use SDK features.</div>
)
);Exports
| Path | Contents |
| --------------------------- | ------------------------------------------- |
| @bardioc/app-sdk | createHostBridge, errors, types, manifest |
| @bardioc/app-sdk/react | AppSdkProvider, hooks (useSdk, etc.) |
| @bardioc/app-sdk/dev | standalone dev bridge helpers |
| @bardioc/app-sdk/types | Type-only TypeScript exports |
| @bardioc/app-sdk/protocol | SDK_MSG constants, wire format types |
| @bardioc/app-sdk/vite | bardiocApp() Vite plugin |
Key exports
// Bridge and types
import {
createHostBridge,
type HostBridge,
type HostBridgeConfig,
type GraphNodeRaw,
type GraphEdgeRaw,
type UserProfile,
type OrgStructure,
} from '@bardioc/app-sdk';
// Errors
import {
EntityNotFoundError,
ValidationError,
PermissionError,
TimeoutError,
NetworkError,
SdkError,
} from '@bardioc/app-sdk';
// React hooks
import { AppSdkProvider, useSdk, useNotify, useSendToKernel } from '@bardioc/app-sdk/react';
// Standalone dev helpers
import { installDevBridge, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';Vite plugin
// vite.config.ts
import { bardiocApp } from '@bardioc/app-sdk/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react(), bardiocApp({ appName: 'my-app', port: 3005 })],
});Handles dev base path, port config, host session proxy routes, platform fonts, and manifest validation on build.
Platform fonts
The Bardioc platform guarantees a stable font endpoint wherever an app runs — like /api/transport, it is a public contract that will never be renamed or removed. Load it with one line in index.html and do not bundle font packages into your app:
<link rel="stylesheet" href="/fonts/bardioc-fonts.css" />It provides Nunito Sans Variable (normal + italic) and Geist Mono Variable as variable fonts (latin subset), matching the --font-nunito-sans / --font-geist-mono theme variables that @bardioc/ui resolves by default.
- Embedded in the OS the URL resolves same-origin against the host, which serves it with immutable caching — one download shared by the host and every app.
- Standalone dev the
bardiocApp()plugin serves the same files from assets shipped inside this package (dev server only — they are never added to your bundle). This applies to Vite-based apps; the angular and nextjs templates run their own dev servers, where the link is a harmless 404 standalone and resolves normally embedded in the OS.
New apps scaffolded with create-bardioc-app include the <link> already.
Standalone Dev Session
Standalone local development is host-backed only.
- Set
DEV_SECRETandAPI_BASE_URLin your local.env. - Run
pnpm dev.
The Vite plugin proxies /rest/* requests to API_BASE_URL with the X-Dev-Secret header, and installDevBridge() handles SDK messages when running in standalone dev.
Dev servers that cannot host the Vite plugin (angular, nextjs) run the same proxy as a standalone server:
import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
// Reads DEV_SECRET / API_BASE_URL from the project's .env files; resolves to null when unset.
await startDevAuthProxy({ projectDir: process.cwd(), port: 3105 });Point the app at it with installDevBridge({ apiBaseUrl: 'http://127.0.0.1:3105/rest' }).
React hooks
useSdk()— raw bridge instanceuseNotify()— fire-and-forget toastuseSendToKernel()— proxy a whitelisted message to the OS service worker
IndexedDB persistence
useSdk() exposes bridge.idb(storeName) for app-scoped persistence:
const drafts = bridge.idb('drafts');
await drafts.put('welcome', {
title: 'Q2 rollout',
summary: 'Publish SDK changes before updating the host workspace.',
});
const draft = await drafts.get<{ title: string; summary: string }>('welcome');
const items = await drafts.query({ prefix: 'wel', limit: 20 });
await drafts.delete('welcome');Each handle is scoped by the host to the app's manifest id, so two apps can use the same storeName without colliding.
Transport API
The bridge provides namespaced transport APIs for graph database and OS operations.
Note: For multiple graph/OS instances or contexts, the Host OS is responsible for routing requests. The SDK proxies all transport requests to the host via postMessage - one bridge = one iframe = one host connection.
Graph API
Access the graph database via bridge.transport.graph.*:
const sdk = useSdk();
// Get single node
const node = await sdk.transport.graph.get<GraphNodeRaw>('node-id');
// Query with Lucene
const people = await sdk.transport.graph.query<GraphNodeRaw>(
'ogit/_type:ogit/Person AND ogit/name:John*',
{ limit: 50, offset: 0, scopeId: 'instance-scope-id' }
);
// Gremlin traversal
const edges = await sdk.transport.graph.gremlin(rootId, "outE('ogit/relates')");
// Create node
const newNode = await sdk.transport.graph.create('ogit/Person', {
'ogit/name': 'Jane Doe',
'ogit/email': '[email protected]',
});
// Update node
await sdk.transport.graph.update('node-id', {
'ogit/name': 'Updated Name',
});
// Create edge
await sdk.transport.graph.connect(fromId, toId, 'ogit/relates');
// Delete node
await sdk.transport.graph.delete('node-id');
// Combined query: Elasticsearch picks the start vertices, Gremlin traverses from them
const speeches = await sdk.transport.graph.combined(
'+(ogit\\/_type: "ogit/Politics/AgendaItem")',
"inE().outV().has('ogit/_type', 'ogit/Politics/Discussion')",
{ limit: 1000 }
);
// Time series
const values = await sdk.transport.graph.timeseries.get('ts-id', {
from: '2026-01-01',
to: '2026-01-31',
});
// Several series in one request, averaged per timestamp
const merged = await sdk.transport.graph.timeseries.get('ts-id', {
from: '2026-01-01',
to: '2026-01-31',
withIds: ['ts-id-2', 'ts-id-3'],
aggregate: 'avg',
});Available methods: get, getMany, getByXid, query, gremlin, combined, create, update, delete, connect, history, batch.delete, timeseries.get, timeseries.add, timeseries.query, timeseries.history, content.set, content.get.
combined and multi-ID time series reads both return one flat result set: a combined query carries
no marker for which start vertex a row came from (label and select() in the traversal when you need
it), and time series entries carry only timestamp and value (read one ID per request when values
must stay attributable).
All graph request option types accept optional scopeId when a request must target a specific scope.
OS API
Access OS features via bridge.transport.os.*:
const sdk = useSdk();
// User profile
const profile = await sdk.transport.os.profile.get();
const avatar = await sdk.transport.os.profile.getAvatar();
await sdk.transport.os.profile.setAvatar(imageBlob);
// Organization
const org = await sdk.transport.os.organization.getStructure();
const unit = await sdk.transport.os.organization.createUnit({
name: 'Engineering',
parentId: 'parent-unit-id',
});
// Applications
const apps = await sdk.transport.os.applications.list();
const app = await sdk.transport.os.applications.get('app-id');
await sdk.transport.os.applications.upload('app-id', zipBlob);
await sdk.transport.os.request({
path: '/custom/endpoint',
method: 'GET',
scopeId: 'instance-scope-id',
});Available namespaces:
os.profile.*—get,getAvatar,setAvatar,getInstancesos.organization.*—getStructure,createUnit,updateUnit,createMember,updateMember,deleteMember,inviteMemberos.applications.*—list,get,upload,download,getFiles,getFile,getConfigurations
Error handling
All transport methods throw structured errors:
import {
EntityNotFoundError,
ValidationError,
PermissionError,
TimeoutError,
NetworkError,
SdkError,
} from '@bardioc/app-sdk';
try {
const node = await sdk.transport.graph.get('node-id');
} catch (error) {
if (error instanceof EntityNotFoundError) {
console.log('Node not found:', error.context.id);
} else if (error instanceof ValidationError) {
console.log('Invalid data:', error.context?.field);
} else if (error instanceof PermissionError) {
console.log('Missing permission:', error.context.permission);
} else if (error instanceof TimeoutError) {
console.log('Request timed out:', error.context.operation);
} else if (error instanceof NetworkError) {
console.log('Network error:', error.statusCode, error.message);
}
}All errors extend SdkError with properties: code, statusCode, context, message.
App manifest
Every app must include public/app-manifest.json:
{
"manifestVersion": 2,
"id": "my-app",
"name": "My App",
"version": "1.0.0",
"permissions": ["notify", "transport"]
}Available permissions:
notify— Show toast notifications viabridge.notify()storage— Key-value storage viabridge.storage.*indexdb— App-scoped IndexedDB viabridge.idb()kernel-proxy— Message the OS service worker viabridge.sendToKernel()transport— Access graph and OS APIs viabridge.transport.*
The Vite plugin validates this automatically and fails the build when the manifest is missing or invalid.
Window
Optionally control how the OS frames the app via a window block:
{
"manifestVersion": 2,
"id": "my-app",
"name": "My App",
"version": "1.0.0",
"permissions": ["notify"],
"window": {
"size": "md",
"resizable": true
}
}size picks a tier (default md):
| Tier | Size (w × h) | Min (w × h) |
| ---- | ------------ | ----------- |
| xs | 480 × 320 | 360 × 280 |
| sm | 640 × 480 | 480 × 320 |
| md | 800 × 600 | 480 × 320 |
| lg | 1024 × 800 | 640 × 480 |
| xl | 1280 × 800 | 800 × 600 |
Instead of a tier, size may be a custom dimensions object — { width, height } (px), with optional minWidth/minHeight (each defaults to a floor when omitted). Custom dimensions are literal, so orientation does not swap them:
{
"window": { "size": { "width": 720, "height": 540, "minWidth": 400, "minHeight": 300 } }
}Other flags (all optional booleans): canHaveMultipleWindows, isInstanceAware, resizable, maximizable, minimizable, customScroll, centered, isHeaderless.
Support
For support, contact [email protected].
License
MIT.
Copyright (c) 2026 ALMATO AG. All rights reserved.
This is an internal library for ALMATO AG.
