@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 +150 -238
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/lib/api/base-repository.d.ts +8 -8
- package/dist/lib/query/query.builder.d.ts +6 -1
- package/dist/modules/beneficiary/beneficiary-info.model.d.ts +82 -42
- package/dist/modules/beneficiary/beneficiary.model.d.ts +11 -2
- package/dist/modules/beneficiary/index.d.ts +2 -2
- package/dist/modules/login/permission.type.d.ts +2 -2
- package/dist/modules/payout/payout-channel-handler.d.ts +7 -6
- package/dist/modules/payout/payout.query.d.ts +54 -3
- package/dist/modules/wallet/narration.model.d.ts +2 -2
- package/dist/modules/wallet/wallet.query.d.ts +31 -0
- package/dist/modules/wallet/wallet.repository.d.ts +2 -2
- package/package.json +3 -4
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
|
-
* **
|
|
12
|
-
*
|
|
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
|
-
###
|
|
28
|
-
|
|
29
|
-
#### Client-Side Usage
|
|
23
|
+
### Login
|
|
30
24
|
|
|
31
|
-
|
|
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 {
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
39
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
40
|
+
console.error("Login failed:", message);
|
|
54
41
|
}
|
|
42
|
+
```
|
|
55
43
|
|
|
56
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
55
|
+
### Repository Usage
|
|
74
56
|
|
|
75
|
-
|
|
57
|
+
Authenticated repositories share the same construction shape:
|
|
76
58
|
|
|
77
59
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
66
|
+
Use this shape for:
|
|
106
67
|
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
import { WalletRepository } from "@temboplus/afloat";
|
|
78
|
+
#### Wallet Example
|
|
113
79
|
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
```typescript
|
|
81
|
+
import { Permissions, WalletRepository } from "@temboplus/afloat";
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const walletRepo = new WalletRepository({ token: auth.getUserToken() });
|
|
83
|
+
const walletRepo = new WalletRepository({ token });
|
|
84
|
+
const [wallet] = await walletRepo.getWallets();
|
|
120
85
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
132
|
-
const
|
|
90
|
+
if (user.can(Permissions.Wallet.ViewBalance)) {
|
|
91
|
+
const balance = await walletRepo.getBalance({ wallet });
|
|
92
|
+
console.log(balance.label);
|
|
93
|
+
}
|
|
133
94
|
|
|
134
|
-
|
|
135
|
-
const entries = await walletRepo.getStatement({
|
|
95
|
+
const entries = await walletRepo.getStatement({
|
|
136
96
|
wallet,
|
|
137
97
|
range: {
|
|
138
|
-
startDate: new Date(
|
|
139
|
-
endDate: new Date(
|
|
140
|
-
}
|
|
98
|
+
startDate: new Date("2024-01-01"),
|
|
99
|
+
endDate: new Date("2024-01-31"),
|
|
100
|
+
},
|
|
141
101
|
});
|
|
142
102
|
```
|
|
143
103
|
|
|
144
|
-
####
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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:
|
|
128
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
166
129
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
### Client-Side Applications
|
|
148
|
+
### Library Responsibilities
|
|
190
149
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
160
|
+
import {
|
|
161
|
+
AuthRepository,
|
|
162
|
+
ProfileRepository,
|
|
163
|
+
WalletRepository,
|
|
164
|
+
} from "@temboplus/afloat";
|
|
226
165
|
|
|
227
|
-
|
|
228
|
-
const
|
|
166
|
+
const authRepo = new AuthRepository();
|
|
167
|
+
const user = await authRepo.logIn(email, password);
|
|
229
168
|
|
|
230
|
-
|
|
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
|
-
|
|
171
|
+
const walletRepo = new WalletRepository({ token: user.token });
|
|
172
|
+
const profileRepo = new ProfileRepository({ token: user.token });
|
|
173
|
+
```
|
|
251
174
|
|
|
252
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
#### `
|
|
190
|
+
#### `AfloatAuth` Import Errors
|
|
273
191
|
|
|
274
|
-
|
|
275
|
-
```bash
|
|
276
|
-
npm install zustand@^4.5.7
|
|
277
|
-
```
|
|
192
|
+
**Problem**: `AfloatAuth` is undefined or cannot be imported.
|
|
278
193
|
|
|
279
|
-
|
|
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
|
-
|
|
291
|
-
const auth = AfloatAuth.instance; // This is client-side only!
|
|
197
|
+
import { AuthRepository } from "@temboplus/afloat";
|
|
292
198
|
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
const
|
|
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
|
|
301
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
|
|
218
|
+
#### Identity Repository Confusion
|
|
312
219
|
|
|
313
|
-
|
|
220
|
+
**Problem**: `IdentityRepository` is used for login.
|
|
314
221
|
|
|
315
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
- `token` - Authentication token for API calls
|
|
339
|
-
- `root` - Custom API root URL
|
|
247
|
+
Available repositories:
|
|
340
248
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|