@temboplus/afloat
v0.2.0-beta.6
Published
A foundational library for Temboplus-Afloat projects.
Readme
@temboplus/afloat
A foundational JavaScript/TypeScript library for TemboPlus-Afloat projects, providing abstracted server communication, shared utilities, and standardized data models for consistent development.
Key Features
Abstracted Server Communication
- Simplifies front-end development by abstracting all interactions with the server behind model-specific repositories
- Consuming projects only need to interact with these repositories, decoupling them from the underlying API implementation
Shared Utilities
- Provides a collection of reusable helper functions for common tasks across Afloat projects, such as error handling
Data Models
- Defines standardized data structures and interfaces for consistent data representation throughout the Afloat ecosystem
Enhanced Maintainability
- Centralizes common logic, making it easier to maintain and update across all consuming projects
- Reduces code duplication and improves consistency
Cross-Environment Compatibility
- Works seamlessly in both client-side and server-side environments
- Different patterns for client-side authentication management vs server-side token handling
Usage
Authentication Setup
Client-Side Usage
In client-side applications, use the AfloatAuth singleton for authentication management:
import { AfloatAuth } from "@temboplus/afloat";
// Initialize client auth (typically in your app entry point)
const auth = AfloatAuth.instance;
// Check if user is authenticated
console.log("User authenticated:", auth.isAuthenticated);
// Access current user
const user = auth.currentUser;
if (user) {
console.log(`Logged in as: ${user.email}`);
}
// Login a user
try {
const user = await auth.logIn("[email protected]", "password123");
console.log("Login successful!");
} catch (error) {
console.error("Login failed:", error.message);
}
// Check permissions
if (auth.checkPermission(Permission.ViewBalance)) {
console.log("User can view balance");
}
// React hook for reactive user state
function UserProfile() {
const user = auth.useCurrentUser();
if (!user) {
return <LoginForm />;
}
return <div>Hello, {user.name}!</div>;
}Server-Side Usage
In server-side environments, do not use AfloatAuth. Instead, extract the authentication token from requests and pass it directly to repositories:
import { WalletRepository } from "@temboplus/afloat";
// In a server route handler or API endpoint
async function handleRequest(req, res) {
try {
// Extract token from request headers
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Create repository with explicit token
const walletRepo = new WalletRepository({ token });
// Use repository methods directly
const balance = await walletRepo.getBalance({ wallet });
const wallets = await walletRepo.getWallets();
return res.json({ balance, wallets });
} catch (error) {
console.error('Server request error:', error);
return res.status(500).json({ error: 'Internal server error' });
}
}Using Repositories
Repositories provide a consistent interface for data operations across environments.
Client-Side Repository Usage
import { WalletRepository } from "@temboplus/afloat";
// Option 1: Let repository use service locator (default behavior)
const walletRepo = new WalletRepository();
// Option 2: Explicitly pass token from auth manager
const auth = AfloatAuth.instance;
const walletRepo = new WalletRepository({ token: auth.getUserToken() });
// Use repository methods
async function displayBalance() {
try {
const balance = await walletRepo.getBalance({ wallet });
console.log(`Current balance: ${balance.value} ${balance.currency}`);
} catch (error) {
console.error('Error fetching balance:', error);
}
}
// Get all wallets
const wallets = await walletRepo.getWallets();
// Get wallet statement
const entries = await walletRepo.getStatement({
wallet,
range: {
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31')
}
});Server-Side Repository Usage
import { WalletRepository } from "@temboplus/afloat";
async function processServerRequest(token: string) {
// Create repository with explicit token (no auth manager needed)
const walletRepo = new WalletRepository({ token });
// Use repository methods directly
const balance = await walletRepo.getBalance({ wallet });
const wallets = await walletRepo.getWallets();
return { balance, wallets };
}
// In Express.js middleware
app.use('/api/wallet', async (req, res, next) => {
const token = extractTokenFromRequest(req);
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
req.walletRepo = new WalletRepository({ token });
next();
});Architecture Overview
Client-Side Pattern
- Authentication Management: Use
AfloatAuth.instancesingleton - Repository Creation: Can use service locator or explicit token passing
- State Management: Reactive updates through React hooks
- Permission Checking: Built into the auth manager
Server-Side Pattern
- No Auth Manager: Extract tokens directly from requests
- Repository Creation: Always pass token explicitly
- Stateless: Each request creates fresh repository instances
- Permission Checking: Handle at route/middleware level
Best Practices
Client-Side Applications
- Initialize Early: Set up
AfloatAuth.instanceearly in your application lifecycle - Use React Hooks: Leverage
auth.useCurrentUser()for reactive UI updates - Handle Permissions: Check permissions before attempting restricted operations
- Error Handling: Implement proper error handling for authentication failures
// Good: Initialize auth early
const auth = AfloatAuth.instance;
// Good: Use reactive hooks in components
function Dashboard() {
const user = auth.useCurrentUser();
if (!user) return <LoginPrompt />;
return <UserDashboard user={user} />;
}
// Good: Check permissions before operations
if (auth.checkPermission(Permission.ViewBalance)) {
const repo = new WalletRepository();
const balance = await repo.getBalance({ wallet });
}Server-Side Applications
- Token Extraction: Always extract and validate tokens from requests
- Explicit Dependencies: Pass tokens explicitly to repositories
- Error Handling: Implement proper middleware for authentication errors
- Stateless Design: Create fresh repository instances per request
// Good: Extract token from request
const token = req.headers.authorization?.replace('Bearer ', '');
// Good: Explicit token passing
const repo = new WalletRepository({ token });
// Good: Middleware pattern
const authMiddleware = (req, res, next) => {
const token = extractToken(req);
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.authToken = token;
next();
};
// Good: Use in route handlers
app.get('/api/balance', authMiddleware, async (req, res) => {
const repo = new WalletRepository({ token: req.authToken });
const balance = await repo.getBalance({ wallet });
res.json({ balance });
});Testing
- Client-Side: Mock the
AfloatAuthsingleton or use dependency injection - Server-Side: Mock repositories with test tokens
- Integration: Test both authentication flows and repository operations
// Client-side testing
const mockAuth = {
currentUser: testUser,
getUserToken: () => 'test-token',
checkPermission: () => true
};
// Server-side testing
const testRepo = new WalletRepository({ token: 'test-token' });Troubleshooting
Common Issues
"Cannot find package 'react'" in Node.js
This error occurs when using Zustand v5. Ensure you're using Zustand v4:
npm install zustand@^4.5.7Remove any existing v5 installation:
npm uninstall [email protected]Authentication Errors in Server Environment
Problem: Trying to use AfloatAuth.instance in server-side code
Solution: Extract tokens from requests and pass directly to repositories
// ❌ Don't do this in server code
const auth = AfloatAuth.instance; // This is client-side only!
// ✅ Do this instead
const token = req.headers.authorization?.replace('Bearer ', '');
const repo = new WalletRepository({ token });Repository Token Issues
Problem: Repository calls failing with authentication errors Solution: Ensure tokens are properly extracted and passed
// ❌ Missing token
const repo = new WalletRepository(); // May fail in server context
// ✅ Explicit token
const repo = new WalletRepository({ token: extractedToken });Debug Information
Use the auth manager's debug utilities for troubleshooting:
// Client-side debugging
const debugInfo = AfloatAuth.instance.getDebugInfo();
console.log('Auth Debug Info:', debugInfo);API Reference
AfloatAuth (Client-Side Only)
AfloatAuth.instance- Singleton instance for client-side usagecurrentUser- Get current authenticated userisAuthenticated- Check authentication statusgetUserToken()- Get current auth tokenuseCurrentUser()- React hook for reactive user statecheckPermission(perm)- Check user permissionslogIn(email, password)- Authenticate userlogOut()- Clear authentication stateresetPassword(current, new)- Update user password
Repository Pattern
All repositories accept optional configuration:
token- Authentication token for API callsroot- Custom API root URL
Example repositories:
WalletRepository- Wallet operations and balance managementAuthRepository- Authentication operations
