@temboplus/afloat 0.2.0-beta.1 → 0.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,336 +8,248 @@ A foundational JavaScript/TypeScript library for TemboPlus-Afloat projects, prov
8
8
  * Simplifies front-end development by abstracting all interactions with the server behind model-specific repositories
9
9
  * Consuming projects only need to interact with these repositories, decoupling them from the underlying API implementation
10
10
 
11
- * **Shared Utilities**
12
- * Provides a collection of reusable helper functions for common tasks across Afloat projects, such as error handling
11
+ * **Authentication-Agnostic Repositories**
12
+ * The library does not own session state or export an auth singleton
13
+ * Consuming applications log in, store the returned token, and pass that token to repositories
13
14
 
14
15
  * **Data Models**
15
16
  * Defines standardized data structures and interfaces for consistent data representation throughout the Afloat ecosystem
16
17
 
17
- * **Enhanced Maintainability**
18
- * Centralizes common logic, making it easier to maintain and update across all consuming projects
19
- * Reduces code duplication and improves consistency
20
-
21
18
  * **Cross-Environment Compatibility**
22
19
  * Works seamlessly in both client-side and server-side environments
23
- * Different patterns for client-side authentication management vs server-side token handling
24
20
 
25
21
  ## Usage
26
22
 
27
- ### Authentication Setup
28
-
29
- #### Client-Side Usage
23
+ ### Login
30
24
 
31
- In client-side applications, use the `AfloatAuth` singleton for authentication management:
25
+ 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.
32
26
 
33
27
  ```typescript
34
- import { AfloatAuth } from "@temboplus/afloat";
35
-
36
- // Initialize client auth (typically in your app entry point)
37
- const auth = AfloatAuth.instance;
28
+ import { AuthRepository } from "@temboplus/afloat";
38
29
 
39
- // Check if user is authenticated
40
- console.log("User authenticated:", auth.isAuthenticated);
41
-
42
- // Access current user
43
- const user = auth.currentUser;
44
- if (user) {
45
- console.log(`Logged in as: ${user.email}`);
46
- }
47
-
48
- // Login a user
49
30
  try {
50
- const user = await auth.logIn("user@example.com", "password123");
51
- console.log("Login successful!");
31
+ const authRepo = new AuthRepository();
32
+ const user = await authRepo.logIn("user@example.com", "password123");
33
+
34
+ storeSession({
35
+ token: user.token,
36
+ user: user.toJSON(),
37
+ });
52
38
  } catch (error) {
53
- console.error("Login failed:", error.message);
39
+ const message = error instanceof Error ? error.message : "Unknown error";
40
+ console.error("Login failed:", message);
54
41
  }
42
+ ```
55
43
 
56
- // Check permissions
57
- if (auth.checkPermission(Permission.ViewBalance)) {
58
- console.log("User can view balance");
59
- }
44
+ `AuthRepository` also has authenticated methods. Construct it with a token before calling those:
60
45
 
61
- // React hook for reactive user state
62
- function UserProfile() {
63
- const user = auth.useCurrentUser();
64
-
65
- if (!user) {
66
- return <LoginForm />;
67
- }
68
-
69
- return <div>Hello, {user.name}!</div>;
70
- }
46
+ ```typescript
47
+ import { AuthRepository } from "@temboplus/afloat";
48
+
49
+ const authRepo = new AuthRepository({ token });
50
+
51
+ await authRepo.updatePassword("currentPassword", "newPassword");
52
+ const accessList = await authRepo.getAccessList();
71
53
  ```
72
54
 
73
- #### Server-Side Usage
55
+ ### Repository Usage
74
56
 
75
- In server-side environments, **do not use `AfloatAuth`**. Instead, extract the authentication token from requests and pass it directly to repositories:
57
+ Authenticated repositories share the same construction shape:
76
58
 
77
59
  ```typescript
78
- import { WalletRepository } from "@temboplus/afloat";
79
-
80
- // In a server route handler or API endpoint
81
- async function handleRequest(req, res) {
82
- try {
83
- // Extract token from request headers
84
- const token = req.headers.authorization?.replace('Bearer ', '');
85
-
86
- if (!token) {
87
- return res.status(401).json({ error: 'Unauthorized' });
88
- }
89
-
90
- // Create repository with explicit token
91
- const walletRepo = new WalletRepository({ token });
92
-
93
- // Use repository methods directly
94
- const balance = await walletRepo.getBalance({ wallet });
95
- const wallets = await walletRepo.getWallets();
96
-
97
- return res.json({ balance, wallets });
98
- } catch (error) {
99
- console.error('Server request error:', error);
100
- return res.status(500).json({ error: 'Internal server error' });
101
- }
102
- }
60
+ const repo = new SomeRepository({
61
+ token: "user-auth-token",
62
+ root: "https://api.afloat.money/v1", // optional
63
+ });
103
64
  ```
104
65
 
105
- ### Using Repositories
66
+ Use this shape for:
106
67
 
107
- Repositories provide a consistent interface for data operations across environments.
68
+ * `WalletRepository`
69
+ * `PayoutRepository`
70
+ * `BeneficiaryRepository`
71
+ * `ProfileRepository`
72
+ * `TeamMemberRepository`
73
+ * `IdentityRepository`
74
+ * `AuthRepository` when calling authenticated methods like `updatePassword` or `getAccessList`
108
75
 
109
- #### Client-Side Repository Usage
76
+ 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.
110
77
 
111
- ```typescript
112
- import { WalletRepository } from "@temboplus/afloat";
78
+ #### Wallet Example
113
79
 
114
- // Option 1: Let repository use service locator (default behavior)
115
- const walletRepo = new WalletRepository();
80
+ ```typescript
81
+ import { Permissions, WalletRepository } from "@temboplus/afloat";
116
82
 
117
- // Option 2: Explicitly pass token from auth manager
118
- const auth = AfloatAuth.instance;
119
- const walletRepo = new WalletRepository({ token: auth.getUserToken() });
83
+ const walletRepo = new WalletRepository({ token });
84
+ const [wallet] = await walletRepo.getWallets();
120
85
 
121
- // Use repository methods
122
- async function displayBalance() {
123
- try {
124
- const balance = await walletRepo.getBalance({ wallet });
125
- console.log(`Current balance: ${balance.value} ${balance.currency}`);
126
- } catch (error) {
127
- console.error('Error fetching balance:', error);
128
- }
86
+ if (!wallet) {
87
+ throw new Error("No wallet available");
129
88
  }
130
89
 
131
- // Get all wallets
132
- const wallets = await walletRepo.getWallets();
90
+ if (user.can(Permissions.Wallet.ViewBalance)) {
91
+ const balance = await walletRepo.getBalance({ wallet });
92
+ console.log(balance.label);
93
+ }
133
94
 
134
- // Get wallet statement
135
- const entries = await walletRepo.getStatement({
95
+ const entries = await walletRepo.getStatement({
136
96
  wallet,
137
97
  range: {
138
- startDate: new Date('2024-01-01'),
139
- endDate: new Date('2024-01-31')
140
- }
98
+ startDate: new Date("2024-01-01"),
99
+ endDate: new Date("2024-01-31"),
100
+ },
141
101
  });
142
102
  ```
143
103
 
144
- #### Server-Side Repository Usage
104
+ #### Identity Example
105
+
106
+ `IdentityRepository` reads the authenticated `/login/me` data. It is not the login request itself, so it requires a token.
107
+
108
+ ```typescript
109
+ import { IdentityRepository } from "@temboplus/afloat";
110
+
111
+ const identityRepo = new IdentityRepository({ token });
112
+ const identity = await identityRepo.getIdentity();
113
+ ```
114
+
115
+ #### Server Route Example
145
116
 
146
117
  ```typescript
147
118
  import { WalletRepository } from "@temboplus/afloat";
148
119
 
149
- async function processServerRequest(token: string) {
150
- // Create repository with explicit token (no auth manager needed)
151
- const walletRepo = new WalletRepository({ token });
152
-
153
- // Use repository methods directly
154
- const balance = await walletRepo.getBalance({ wallet });
155
- const wallets = await walletRepo.getWallets();
156
-
157
- return { balance, wallets };
120
+ function extractBearerToken(req): string | undefined {
121
+ return req.headers.authorization?.replace(/^Bearer\s+/i, "");
158
122
  }
159
123
 
160
- // In Express.js middleware
161
- app.use('/api/wallet', async (req, res, next) => {
162
- const token = extractTokenFromRequest(req);
163
-
124
+ app.get("/api/wallets", async (req, res) => {
125
+ const token = extractBearerToken(req);
126
+
164
127
  if (!token) {
165
- return res.status(401).json({ error: 'No token provided' });
128
+ return res.status(401).json({ error: "Unauthorized" });
166
129
  }
167
-
168
- req.walletRepo = new WalletRepository({ token });
169
- next();
130
+
131
+ const walletRepo = new WalletRepository({ token });
132
+ const wallets = await walletRepo.getWallets();
133
+
134
+ return res.json({ wallets });
170
135
  });
171
136
  ```
172
137
 
173
138
  ## Architecture Overview
174
139
 
175
- ### Client-Side Pattern
176
- - **Authentication Management**: Use `AfloatAuth.instance` singleton
177
- - **Repository Creation**: Can use service locator or explicit token passing
178
- - **State Management**: Reactive updates through React hooks
179
- - **Permission Checking**: Built into the auth manager
140
+ ### Host Application Responsibilities
180
141
 
181
- ### Server-Side Pattern
182
- - **No Auth Manager**: Extract tokens directly from requests
183
- - **Repository Creation**: Always pass token explicitly
184
- - **Stateless**: Each request creates fresh repository instances
185
- - **Permission Checking**: Handle at route/middleware level
142
+ * Call `AuthRepository.logIn(...)` to create a session
143
+ * Store the returned token in your own state, cookie, storage, or server session
144
+ * Rehydrate `User` with `User.fromJSON(...)` when needed
145
+ * Pass `{ token }` to repositories before calling authenticated endpoints
146
+ * Check permissions with the returned `User` instance, for example `user.can(Permissions.Wallet.ViewBalance)`
186
147
 
187
- ## Best Practices
188
-
189
- ### Client-Side Applications
148
+ ### Library Responsibilities
190
149
 
191
- 1. **Initialize Early**: Set up `AfloatAuth.instance` early in your application lifecycle
192
- 2. **Use React Hooks**: Leverage `auth.useCurrentUser()` for reactive UI updates
193
- 3. **Handle Permissions**: Check permissions before attempting restricted operations
194
- 4. **Error Handling**: Implement proper error handling for authentication failures
150
+ * Provide typed repositories for Afloat API resources
151
+ * Attach the token and request ID headers to repository calls
152
+ * Convert API responses into domain models where repositories support it
153
+ * Surface API errors through `APIError` or repository-specific errors
195
154
 
196
- ```typescript
197
- // Good: Initialize auth early
198
- const auth = AfloatAuth.instance;
199
-
200
- // Good: Use reactive hooks in components
201
- function Dashboard() {
202
- const user = auth.useCurrentUser();
203
-
204
- if (!user) return <LoginPrompt />;
205
-
206
- return <UserDashboard user={user} />;
207
- }
208
-
209
- // Good: Check permissions before operations
210
- if (auth.checkPermission(Permission.ViewBalance)) {
211
- const repo = new WalletRepository();
212
- const balance = await repo.getBalance({ wallet });
213
- }
214
- ```
215
-
216
- ### Server-Side Applications
155
+ ## Best Practices
217
156
 
218
- 1. **Token Extraction**: Always extract and validate tokens from requests
219
- 2. **Explicit Dependencies**: Pass tokens explicitly to repositories
220
- 3. **Error Handling**: Implement proper middleware for authentication errors
221
- 4. **Stateless Design**: Create fresh repository instances per request
157
+ ### Token Handling
222
158
 
223
159
  ```typescript
224
- // Good: Extract token from request
225
- const token = req.headers.authorization?.replace('Bearer ', '');
160
+ import {
161
+ AuthRepository,
162
+ ProfileRepository,
163
+ WalletRepository,
164
+ } from "@temboplus/afloat";
226
165
 
227
- // Good: Explicit token passing
228
- const repo = new WalletRepository({ token });
166
+ const authRepo = new AuthRepository();
167
+ const user = await authRepo.logIn(email, password);
229
168
 
230
- // Good: Middleware pattern
231
- const authMiddleware = (req, res, next) => {
232
- const token = extractToken(req);
233
-
234
- if (!token) {
235
- return res.status(401).json({ error: 'Unauthorized' });
236
- }
237
-
238
- req.authToken = token;
239
- next();
240
- };
241
-
242
- // Good: Use in route handlers
243
- app.get('/api/balance', authMiddleware, async (req, res) => {
244
- const repo = new WalletRepository({ token: req.authToken });
245
- const balance = await repo.getBalance({ wallet });
246
- res.json({ balance });
247
- });
248
- ```
169
+ saveToken(user.token);
249
170
 
250
- ### Testing
171
+ const walletRepo = new WalletRepository({ token: user.token });
172
+ const profileRepo = new ProfileRepository({ token: user.token });
173
+ ```
251
174
 
252
- 1. **Client-Side**: Mock the `AfloatAuth` singleton or use dependency injection
253
- 2. **Server-Side**: Mock repositories with test tokens
254
- 3. **Integration**: Test both authentication flows and repository operations
175
+ ### Permission Checks
255
176
 
256
177
  ```typescript
257
- // Client-side testing
258
- const mockAuth = {
259
- currentUser: testUser,
260
- getUserToken: () => 'test-token',
261
- checkPermission: () => true
262
- };
263
-
264
- // Server-side testing
265
- const testRepo = new WalletRepository({ token: 'test-token' });
178
+ import { Permissions, PayoutRepository } from "@temboplus/afloat";
179
+
180
+ if (user.can(Permissions.Payout.Create)) {
181
+ const payoutRepo = new PayoutRepository({ token: user.token });
182
+ await payoutRepo.pay(input);
183
+ }
266
184
  ```
267
185
 
268
186
  ## Troubleshooting
269
187
 
270
188
  ### Common Issues
271
189
 
272
- #### `"Cannot find package 'react'" in Node.js`
190
+ #### `AfloatAuth` Import Errors
273
191
 
274
- This error occurs when using Zustand v5. Ensure you're using Zustand v4:
275
- ```bash
276
- npm install zustand@^4.5.7
277
- ```
192
+ **Problem**: `AfloatAuth` is undefined or cannot be imported.
278
193
 
279
- Remove any existing v5 installation:
280
- ```bash
281
- npm uninstall zustand@5.x.x
282
- ```
283
-
284
- #### Authentication Errors in Server Environment
285
-
286
- **Problem**: Trying to use `AfloatAuth.instance` in server-side code
287
- **Solution**: Extract tokens from requests and pass directly to repositories
194
+ **Solution**: Replace `AfloatAuth` usage with `AuthRepository.logIn(...)` plus application-owned session state.
288
195
 
289
196
  ```typescript
290
- // Don't do this in server code
291
- const auth = AfloatAuth.instance; // This is client-side only!
197
+ import { AuthRepository } from "@temboplus/afloat";
292
198
 
293
- // Do this instead
294
- const token = req.headers.authorization?.replace('Bearer ', '');
295
- const repo = new WalletRepository({ token });
199
+ const authRepo = new AuthRepository();
200
+ const user = await authRepo.logIn(email, password);
201
+ const token = user.token;
296
202
  ```
297
203
 
298
204
  #### Repository Token Issues
299
205
 
300
- **Problem**: Repository calls failing with authentication errors
301
- **Solution**: Ensure tokens are properly extracted and passed
206
+ **Problem**: Repository calls fail with authentication errors.
207
+
208
+ **Solution**: Ensure the token from login or the incoming request is passed into the repository.
302
209
 
303
210
  ```typescript
304
- // Missing token
305
- const repo = new WalletRepository(); // May fail in server context
211
+ if (!extractedToken) {
212
+ throw new Error("Missing authentication token");
213
+ }
306
214
 
307
- // ✅ Explicit token
308
215
  const repo = new WalletRepository({ token: extractedToken });
309
216
  ```
310
217
 
311
- ### Debug Information
218
+ #### Identity Repository Confusion
312
219
 
313
- Use the auth manager's debug utilities for troubleshooting:
220
+ **Problem**: `IdentityRepository` is used for login.
314
221
 
315
- ```typescript
316
- // Client-side debugging
317
- const debugInfo = AfloatAuth.instance.getDebugInfo();
318
- console.log('Auth Debug Info:', debugInfo);
319
- ```
222
+ **Solution**: Use `AuthRepository.logIn(...)` for login. Use `IdentityRepository({ token }).getIdentity()` only after you have a token.
320
223
 
321
224
  ## API Reference
322
225
 
323
- ### AfloatAuth (Client-Side Only)
226
+ ### Login and Auth
227
+
228
+ * `AuthRepository.logIn(email, password)` - Authenticate without an existing token and return a `User`
229
+ * `AuthRepository({ token }).updatePassword(current, next)` - Update the current user's password
230
+ * `AuthRepository({ token }).getAccessList()` - Fetch the current user's access list
231
+ * `User.token` - Token to pass into repositories
232
+ * `User.can(permission)` - Check a single permission
233
+ * `User.canAny(permissions)` - Check if the user has at least one permission
234
+ * `User.canAll(permissions)` - Check if the user has all permissions
324
235
 
325
- - `AfloatAuth.instance` - Singleton instance for client-side usage
326
- - `currentUser` - Get current authenticated user
327
- - `isAuthenticated` - Check authentication status
328
- - `getUserToken()` - Get current auth token
329
- - `useCurrentUser()` - React hook for reactive user state
330
- - `checkPermission(perm)` - Check user permissions
331
- - `logIn(email, password)` - Authenticate user
332
- - `logOut()` - Clear authentication state
333
- - `resetPassword(current, new)` - Update user password
236
+ ### Authenticated Repositories
334
237
 
335
- ### Repository Pattern
238
+ All authenticated repositories use:
239
+
240
+ ```typescript
241
+ new RepositoryName({
242
+ token: "user-auth-token",
243
+ root: "custom-api-root", // optional
244
+ });
245
+ ```
336
246
 
337
- All repositories accept optional configuration:
338
- - `token` - Authentication token for API calls
339
- - `root` - Custom API root URL
247
+ Available repositories:
340
248
 
341
- Example repositories:
342
- - `WalletRepository` - Wallet operations and balance management
343
- - `AuthRepository` - Authentication operations
249
+ * `WalletRepository` - Wallet operations, balances, and statements
250
+ * `PayoutRepository` - Payout creation, approval, rejection, lookup, and counting
251
+ * `BeneficiaryRepository` - Beneficiary create, edit, remove, and lookup
252
+ * `ProfileRepository` - Current profile lookup
253
+ * `TeamMemberRepository` - Team member and role management
254
+ * `IdentityRepository` - Current `/login/me` identity data
255
+ * `AuthRepository` - Login without token; password and access-list operations with token