- 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.
344 lines
10 KiB
Markdown
344 lines
10 KiB
Markdown
# @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 Node’s 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.
|
||
|