Files
bface/README.md
Amer Agovic 94a9f32969 Initial commit: bface library, build fixes, and refreshed docs
- Externalize all @tamagui/* and tamagui subpaths so dist no longer vendors Tamagui.
- Emit TypeScript declarations with vite-plugin-dts; fix package exports types for ui/*.
- Align initEnv with profiles: displayName, brandLogo, api.baseURL, themeColor, uiShell.
- Stabilize tests with Node localStorage file; env tests pass.
- Update README and component docs for services, menus, API client, and development.
2026-04-18 10:43:52 -05:00

344 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# @reliancy/bface
Base UI and platform library for building Progressive Web Applications (PWAs) with React and Tamagui: shells, routing, env/config, storage, menus, security primitives, and data helpers.
## Installation
```bash
npm install @reliancy/bface
```
If your organization hosts this package on a private registry, configure npm for that registry (see `publishConfig` in `package.json`) before installing.
## Peer dependencies
Install React in the consuming app (versions should match `peerDependencies` in `package.json`):
```bash
npm install react react-dom
```
Tamagui packages are **dependencies** of `@reliancy/bface`, so you do not need to add `@tamagui/*` or `tamagui` separately unless you want a single shared version across packages (then align versions with this library).
## Quick Start
### 1. Basic App Setup
The library provides an `App` component that manages the application shell, routing, theming, and platform services.
```jsx
import { App } from '@reliancy/bface/ui/App';
import { CONFIG_KEYS } from '@reliancy/bface/platform/env';
import { registerServiceWorker } from '@reliancy/bface/platform/sw-register';
async function handleInit(services, { initialProfile } = {}) {
// Application profile (camelCase or snake_case field names are accepted where noted in env mapping)
const profile = {
name: 'MyApp',
displayName: 'My Application',
brandLogo: '/logo.svg',
ui_shell: 'DashboardShell',
modules: ['core'],
storage: { backend: 'localStorage' },
api: { baseURL: '/api' }
};
services.env.initEnv(profile);
for (const moduleName of profile.modules) {
// await loadModule(moduleName, services);
}
await registerServiceWorker();
return profile;
}
function MyApp() {
return <App onInit={handleInit} />;
}
```
### 2. Using platform services
`App` calls `onInit(services, { initialProfile })` once platform services exist. The `services` object includes:
- **`services.api_client`** — HTTP client (`get`, `post`, …)
- **`services.storage`** — storage module (`getProvider`, …)
- **`services.api_router`** — placeholder for service-worker API routing (when available)
- **`services.ui_router`** — UI routing helpers
- **`services.menu`** — menu registration and queries
- **`services.env`** — `initEnv`, `getConfig`, `setConfig`, `CONFIG_KEYS`, tracing helpers, etc.
Service worker registration is **not** on `services`; import `registerServiceWorker` from `@reliancy/bface/platform/sw-register` (or the package root) and call it from `onInit` when you are ready.
```jsx
async function handleInit(services) {
// Example: Get configuration
const appName = await services.env.getConfig(CONFIG_KEYS.APP_NAME);
// Example: Make API call
const data = await services.api_client.get('/users');
// Example: Store data (KeyValueStore via getProvider)
const kv = services.storage.getProvider('kv', 'myStore');
await kv.set('user', { id: 1, name: 'John' });
}
```
### 3. Using UI Components
Import and use UI components directly:
```jsx
import { Page, Panel, MenuItemButton } from '@reliancy/bface/ui/components';
import { SideBar, TopBar } from '@reliancy/bface/ui/components';
function MyPage() {
return (
<Page title="Dashboard" icon="dashboard">
<Panel>
<Text>Welcome to your dashboard</Text>
</Panel>
</Page>
);
}
```
### 4. Using Platform Modules
Import platform utilities:
```jsx
import { getConfig, setConfig, CONFIG_KEYS } from '@reliancy/bface/platform/env';
import { api } from '@reliancy/bface/platform/api';
import { getProvider } from '@reliancy/bface/platform/storage';
import { queryMenuItems } from '@reliancy/bface/platform/menu';
// Get configuration
const theme = await getConfig('theme.mode', 'system');
// Set configuration
await setConfig('theme.mode', 'dark');
// Use storage
const storage = getProvider('kv', 'myStore');
await storage.set('key', 'value');
const value = await storage.get('key');
// HTTP (singleton; base URL comes from env when wired in App)
await api.get('/status');
// Query menu items
const menuItems = queryMenuItems('/primary');
```
## Library structure
### Exports (`package.json` → `exports`)
- **`@reliancy/bface`** — main entry: platform, `App`, UI components index, general settings, security, and data helpers
- **`@reliancy/bface/platform/*`** — platform modules (`env`, `api`, `storage`, `menu`, `sw-register`, `compat`, `host`, …)
- **`@reliancy/bface/ui/*`** — UI entry points such as `App` and `components`
Security and data types are re-exported from the root entry; there are no separate `exports` subpaths for `./security/*` or `./data/*` today—import them from `@reliancy/bface` or add deep links if your bundler resolves source.
### Platform modules
- **`platform/env.js`** — profile → config dictionary, `getConfig` / `setConfig`, logging and tracing helpers
- **`platform/api.js`** — API client
- **`platform/storage.js`** — storage abstraction (localStorage, IndexedDB, OPFS)
- **`platform/menu.js`** — menu model and queries
- **`platform/sw-register.js`** — service worker registration and cache helpers
- **`platform/compat.js`** — environment detection and compatibility
- **`platform/host.js`** — host detection (e.g. Electron)
### UI
- **`ui/App.jsx`** — Tamagui provider, theme controller, security bootstrap, shell selection, `onInit`
- **`ui/components/`** — shells (`EmptyShell`, `LandingShell`, `DashboardShell`, …), layout, grid/DirView, forms, router
- **`ui/styles/`** — Tamagui style themes (`material`, `minimal`, `colorful`)
### Other areas
- **`security/`** — policies, models, login and account pages, route guards, `securityService`
- **`data/`** — `DataModel`, `InMemoryDataModel`
### Application profile and `initEnv`
`initEnv` maps the profile object onto internal config keys. For convenience, several fields accept **either** camelCase **or** snake_case:
| Concept | Accepted profile fields | Internal key |
|--------|-------------------------|--------------|
| Display title | `displayName`, then `short_name`, then `name` | `APP_DISPLAY_NAME` |
| Stable app id | `id`, then `name` | `APP_NAME` |
| Logo | `brandLogo` or `brand_logo`, or PWA manifest-style `icons[0].src` | `BRAND_LOGO` |
| API base | `api.baseURL` or `api.base_url` | `API_BASE_URL` |
| Shell | `uiShell` or `ui_shell` | `UI_SHELL` |
### Shell names (`ui_shell` / `UI_SHELL`)
Resolved case-insensitively in `App`:
| Profile value | Component |
|---------------|-----------|
| `EmptyShell` (default) | `EmptyShell` |
| `LandingShell` | `LandingShell` |
| `TopBarShell` | Same layout as `LandingShell` (top bar shell) |
| `DashboardShell` | `DashboardShell` |
## Complete Example
Here's a complete example of using the library in a project:
```jsx
// app.jsx
import { App } from '@reliancy/bface/ui/App';
import { CONFIG_KEYS } from '@reliancy/bface/platform/env';
import { registerServiceWorker } from '@reliancy/bface/platform/sw-register';
async function loadProfile() {
// Load your app profile (from JSON, API, etc.)
const response = await fetch('/profile.json');
return await response.json();
}
async function loadModule(moduleName, services) {
// Dynamically import and initialize your modules
const module = await import(`./modules/${moduleName}/index.js`);
if (module.publishModule) {
module.publishModule(services);
}
}
async function handleInit(services, { initialProfile } = {}) {
// 1. Load profile (use embedded profile from App when provided)
const profile = initialProfile ?? await loadProfile();
// 2. Initialize environment
services.env.initEnv(profile);
// 3. Load modules
const modules = await services.env.getConfig(CONFIG_KEYS.MODULES, []);
for (const moduleName of modules) {
await loadModule(moduleName, services);
}
// 4. Register service worker
await registerServiceWorker();
return profile;
}
export default function MyApp() {
return <App onInit={handleInit} />;
}
```
## Theming
The library supports multiple themes. Configure the theme in your profile:
```json
{
"name": "MyApp",
"ui_shell": "DashboardShell",
"theme": {
"name": "material",
"mode": "system"
}
}
```
Available themes:
- `material` - Material Design theme
- `minimal` - Minimal theme
- `colorful` - Colorful theme
## Menu System
Register menu items in your modules:
```jsx
// In your module
import { publishMenuItem, MENU_DIRS } from '@reliancy/bface/platform/menu';
export function publishModule(platform) {
publishMenuItem(MENU_DIRS.PRIMARY('dashboard'), {
label: 'Dashboard',
icon: 'dashboard',
invoke: () => {
platform.ui_router.navigate('/dashboard');
}
});
}
```
## Storage
The library provides a unified storage API:
```jsx
import { getProvider } from '@reliancy/bface/platform/storage';
// Get storage provider
const storage = getProvider('kv', 'myStore');
// Use storage
await storage.set('key', { data: 'value' });
const value = await storage.get('key');
const exists = await storage.hasKey('key');
await storage.remove('key');
await storage.clear();
```
Supported backends:
- `localStorage` - Browser localStorage
- `indexedDB` - IndexedDB
- `opfs` - Origin Private File System
## API Client
Make HTTP requests using the API client:
```jsx
// In your onInit callback — paths are relative to the API base URL from the profile (`api.baseURL` / `api.base_url`)
const data = await services.api_client.get('/users');
const user = await services.api_client.post('/users', { name: 'John' });
await services.api_client.put('/users/1', { name: 'Jane' });
await services.api_client.delete('/users/1');
```
## Development
Clone the repository, install dependencies (`npm install`), then:
### Build
```bash
npm run build
```
Produces ESM under `dist/` with **`.d.ts` declaration files** (via `vite-plugin-dts`) for the published `exports` map. The `dist/` folder is gitignored in this repo; the npm package tarball is built from `files` in `package.json`.
### Tests
```bash
npm test
```
Uses Nodes built-in test runner. The npm script passes `--localstorage-file=.node-localstorage` so `getConfig` / storage-backed paths work under Node without noisy `SecurityError` warnings.
### Watch mode (library)
```bash
npm run dev
```
Runs `vite build --watch` for iterative work on the package.
## License
Copyright and licensing terms are defined by the organization that publishes this package.