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.
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
# @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.
|
||||
|
||||
Reference in New Issue
Block a user