@temboplus/afloat
v0.2.0-beta.14
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
Authentication-Agnostic Repositories
- The library does not own session state or export an auth singleton
- Consuming applications log in, store the returned token, and pass that token to repositories
Data Models
- Defines standardized data structures and interfaces for consistent data representation throughout the Afloat ecosystem
Cross-Environment Compatibility
- Works seamlessly in both client-side and server-side environments
Usage
Login
Use AuthRepository.logIn(...) for the unauthenticated login request. The returned User contains the token that should be stored by your app and passed to other repositories.
import { AuthRepository } from "@temboplus/afloat";
try {
const authRepo = new AuthRepository();
const user = await authRepo.logIn("[email protected]", "password123");
storeSession({
token: user.token,
user: user.toJSON(),
});
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Login failed:", message);
}AuthRepository also has authenticated methods. Construct it with a token before calling those:
import { AuthRepository } from "@temboplus/afloat";
const authRepo = new AuthRepository({ token });
await authRepo.updatePassword("currentPassword", "newPassword");
const accessList = await authRepo.getAccessList();Repository Usage
Authenticated repositories share the same construction shape:
const repo = new SomeRepository({
token: "user-auth-token",
root: "https://api.afloat.money/v1", // optional
});Use this shape for:
WalletRepositoryPayoutRepositoryBeneficiaryRepositoryProfileRepositoryTeamMemberRepositoryIdentityRepositoryAuthRepositorywhen calling authenticated methods likeupdatePasswordorgetAccessList
The TypeScript constructors accept optional fields because the base repository supports a global token getter, but authenticated Afloat API calls still require a token. Passing { token } explicitly is the recommended and documented integration path.
Wallet Example
import { Permissions, WalletRepository } from "@temboplus/afloat";
const walletRepo = new WalletRepository({ token });
const [wallet] = await walletRepo.getWallets();
if (!wallet) {
throw new Error("No wallet available");
}
if (user.can(Permissions.Wallet.ViewBalance)) {
const balance = await walletRepo.getBalance({ wallet });
console.log(balance.label);
}
const entries = await walletRepo.getStatement({
wallet,
range: {
startDate: new Date("2024-01-01"),
endDate: new Date("2024-01-31"),
},
});Identity Example
IdentityRepository reads the authenticated /login/me data. It is not the login request itself, so it requires a token.
import { IdentityRepository } from "@temboplus/afloat";
const identityRepo = new IdentityRepository({ token });
const identity = await identityRepo.getIdentity();Server Route Example
import { WalletRepository } from "@temboplus/afloat";
function extractBearerToken(req): string | undefined {
return req.headers.authorization?.replace(/^Bearer\s+/i, "");
}
app.get("/api/wallets", async (req, res) => {
const token = extractBearerToken(req);
if (!token) {
return res.status(401).json({ error: "Unauthorized" });
}
const walletRepo = new WalletRepository({ token });
const wallets = await walletRepo.getWallets();
return res.json({ wallets });
});Architecture Overview
Host Application Responsibilities
- Call
AuthRepository.logIn(...)to create a session - Store the returned token in your own state, cookie, storage, or server session
- Rehydrate
UserwithUser.fromJSON(...)when needed - Pass
{ token }to repositories before calling authenticated endpoints - Check permissions with the returned
Userinstance, for exampleuser.can(Permissions.Wallet.ViewBalance)
Library Responsibilities
- Provide typed repositories for Afloat API resources
- Attach the token and request ID headers to repository calls
- Convert API responses into domain models where repositories support it
- Surface API errors through
APIErroror repository-specific errors
Best Practices
Token Handling
import {
AuthRepository,
ProfileRepository,
WalletRepository,
} from "@temboplus/afloat";
const authRepo = new AuthRepository();
const user = await authRepo.logIn(email, password);
saveToken(user.token);
const walletRepo = new WalletRepository({ token: user.token });
const profileRepo = new ProfileRepository({ token: user.token });Permission Checks
import { Permissions, PayoutRepository } from "@temboplus/afloat";
if (user.can(Permissions.Payout.Create)) {
const payoutRepo = new PayoutRepository({ token: user.token });
await payoutRepo.pay(input);
}Troubleshooting
Common Issues
AfloatAuth Import Errors
Problem: AfloatAuth is undefined or cannot be imported.
Solution: Replace AfloatAuth usage with AuthRepository.logIn(...) plus application-owned session state.
import { AuthRepository } from "@temboplus/afloat";
const authRepo = new AuthRepository();
const user = await authRepo.logIn(email, password);
const token = user.token;Repository Token Issues
Problem: Repository calls fail with authentication errors.
Solution: Ensure the token from login or the incoming request is passed into the repository.
if (!extractedToken) {
throw new Error("Missing authentication token");
}
const repo = new WalletRepository({ token: extractedToken });Identity Repository Confusion
Problem: IdentityRepository is used for login.
Solution: Use AuthRepository.logIn(...) for login. Use IdentityRepository({ token }).getIdentity() only after you have a token.
API Reference
Login and Auth
AuthRepository.logIn(email, password)- Authenticate without an existing token and return aUserAuthRepository({ token }).updatePassword(current, next)- Update the current user's passwordAuthRepository({ token }).getAccessList()- Fetch the current user's access listUser.token- Token to pass into repositoriesUser.can(permission)- Check a single permissionUser.canAny(permissions)- Check if the user has at least one permissionUser.canAll(permissions)- Check if the user has all permissions
Authenticated Repositories
All authenticated repositories use:
new RepositoryName({
token: "user-auth-token",
root: "custom-api-root", // optional
});Available repositories:
WalletRepository- Wallet operations, balances, and statementsPayoutRepository- Payout creation, approval, rejection, lookup, and countingBeneficiaryRepository- Beneficiary create, edit, remove, and lookupProfileRepository- Current profile lookupTeamMemberRepository- Team member and role managementIdentityRepository- Current/login/meidentity dataAuthRepository- Login without token; password and access-list operations with token
