@wealthfolio/addon-sdk 3.7.0 → 3.8.0
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/CHANGELOG.md +30 -0
- package/README.md +212 -164
- package/dist/{chunk-67V3WWT6.js → chunk-2IM7BFJX.js} +3 -3
- package/dist/{chunk-67V3WWT6.js.map → chunk-2IM7BFJX.js.map} +1 -1
- package/dist/{chunk-KCG6RCFW.js → chunk-3YZER4XO.js} +16 -2
- package/dist/chunk-3YZER4XO.js.map +1 -0
- package/dist/{chunk-MKLA4V4J.js → chunk-DQNFXUZ7.js} +3 -3
- package/dist/{chunk-MKLA4V4J.js.map → chunk-DQNFXUZ7.js.map} +1 -1
- package/dist/host-dependencies.js +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/permissions.js +1 -1
- package/dist/src/data-types.d.ts +90 -1
- package/dist/src/host-api.d.ts +63 -1
- package/dist/src/host-dependencies.d.ts +2 -2
- package/dist/src/i18n.d.ts +55 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-KCG6RCFW.js.map +0 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ integrations, and visualizations.
|
|
|
42
42
|
- **ESM Support**: Modern ECMAScript modules with tree-shaking support
|
|
43
43
|
- **Comprehensive Logging**: Built-in logging system with multiple levels
|
|
44
44
|
- **Event System**: Subscribe to application events and state changes
|
|
45
|
+
- **Spend Categorization**: Manage reusable expense, income, and savings rules
|
|
46
|
+
- **Localization**: Follow the host locale with scoped addon translations
|
|
45
47
|
- **Performance Optimized**: Lightweight bundle with minimal overhead
|
|
46
48
|
- **Developer Tools**: Built-in debugging and development utilities
|
|
47
49
|
- **Backwards Compatible**: Stable API with semantic versioning
|
|
@@ -125,12 +127,12 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
|
|
|
125
127
|
- **Node.js**: >= 20.0.0
|
|
126
128
|
- **React**: ^19.2.4 (peer dependency and host-provided version)
|
|
127
129
|
- **TypeScript**: ^5.0.0 (recommended for development)
|
|
128
|
-
- **React Query**: ^
|
|
130
|
+
- **React Query**: ^5.90.0 (for data fetching)
|
|
129
131
|
|
|
130
132
|
### Package Information
|
|
131
133
|
|
|
132
134
|
- **Package Name**: `@wealthfolio/addon-sdk`
|
|
133
|
-
- **Current Version**: 3.
|
|
135
|
+
- **Current Version**: 3.8.0
|
|
134
136
|
- **Bundle Format**: ESM (ECMAScript Modules)
|
|
135
137
|
- **Type Definitions**: Included (TypeScript ready)
|
|
136
138
|
- **License**: MIT
|
|
@@ -140,23 +142,28 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
|
|
|
140
142
|
|
|
141
143
|
### Import Methods
|
|
142
144
|
|
|
143
|
-
The SDK supports
|
|
145
|
+
The SDK supports a public entry point plus focused subpath imports:
|
|
144
146
|
|
|
145
147
|
```typescript
|
|
146
|
-
//
|
|
147
|
-
import {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
import {
|
|
148
|
+
// Public entry point (recommended)
|
|
149
|
+
import type {
|
|
150
|
+
AddonContext,
|
|
151
|
+
AddonManifest,
|
|
152
|
+
Holding,
|
|
153
|
+
Permission,
|
|
154
|
+
RiskLevel,
|
|
155
|
+
} from '@wealthfolio/addon-sdk';
|
|
156
|
+
import { PERMISSION_CATEGORIES } from '@wealthfolio/addon-sdk';
|
|
157
|
+
|
|
158
|
+
// Optional module-specific subpath imports
|
|
159
|
+
import type { AddonManifest as Manifest } from '@wealthfolio/addon-sdk/manifest';
|
|
160
|
+
import type { Permission as AddonPermission } from '@wealthfolio/addon-sdk/permissions';
|
|
158
161
|
```
|
|
159
162
|
|
|
163
|
+
The `/types` subpath contains core context, routing, sidebar, and event types.
|
|
164
|
+
Financial data types such as `Account`, `Activity`, and `Holding` are exported
|
|
165
|
+
from the package root.
|
|
166
|
+
|
|
160
167
|
## 🏗️ Project Structure
|
|
161
168
|
|
|
162
169
|
Create your addon with the following recommended structure:
|
|
@@ -233,11 +240,16 @@ Create a `manifest.json` file in your addon root:
|
|
|
233
240
|
"homepage": "https://github.com/yourname/investment-fees-tracker",
|
|
234
241
|
"license": "MIT",
|
|
235
242
|
"main": "dist/addon.js",
|
|
236
|
-
"sdkVersion": "3.
|
|
237
|
-
"minWealthfolioVersion": "3.
|
|
243
|
+
"sdkVersion": "3.8.0",
|
|
244
|
+
"minWealthfolioVersion": "3.8.0",
|
|
238
245
|
"keywords": ["portfolio", "fees", "tracking", "analytics"],
|
|
239
246
|
"icon": "data:image/svg+xml;base64,...",
|
|
240
247
|
"permissions": [
|
|
248
|
+
{
|
|
249
|
+
"category": "accounts",
|
|
250
|
+
"functions": ["getAll"],
|
|
251
|
+
"purpose": "List accounts whose holdings will be analyzed"
|
|
252
|
+
},
|
|
241
253
|
{
|
|
242
254
|
"category": "portfolio",
|
|
243
255
|
"functions": ["getHoldings"],
|
|
@@ -247,6 +259,11 @@ Create a `manifest.json` file in your addon root:
|
|
|
247
259
|
"category": "activities",
|
|
248
260
|
"functions": ["getAll"],
|
|
249
261
|
"purpose": "Analyze transaction history for fee calculations"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"category": "performance",
|
|
265
|
+
"functions": ["calculateSummary"],
|
|
266
|
+
"purpose": "Calculate account performance alongside fee totals"
|
|
250
267
|
}
|
|
251
268
|
]
|
|
252
269
|
}
|
|
@@ -285,7 +302,11 @@ example:
|
|
|
285
302
|
```typescript
|
|
286
303
|
// src/addon.tsx
|
|
287
304
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
288
|
-
import type {
|
|
305
|
+
import type {
|
|
306
|
+
AddonContext,
|
|
307
|
+
AddonEnableFunction,
|
|
308
|
+
QueryClient,
|
|
309
|
+
} from '@wealthfolio/addon-sdk';
|
|
289
310
|
import FeesPage from './pages/fees-page';
|
|
290
311
|
|
|
291
312
|
// Main addon component
|
|
@@ -319,7 +340,7 @@ const enable: AddonEnableFunction = (context) => {
|
|
|
319
340
|
|
|
320
341
|
// Create wrapper component with this addon's QueryClient
|
|
321
342
|
const InvestmentFeesTrackerWrapper = () => {
|
|
322
|
-
const addonQueryClient = context.api.query.getClient();
|
|
343
|
+
const addonQueryClient = context.api.query.getClient() as QueryClient;
|
|
323
344
|
return (
|
|
324
345
|
<QueryClientProvider client={addonQueryClient}>
|
|
325
346
|
<InvestmentFeesTrackerAddon ctx={context} />
|
|
@@ -367,7 +388,7 @@ export default enable;
|
|
|
367
388
|
|
|
368
389
|
1. **Addon Query Client**: Uses `context.api.query.getClient()` for local data
|
|
369
390
|
fetching with host invalidation bridging
|
|
370
|
-
2. **UI Icons**:
|
|
391
|
+
2. **UI Icons**: Uses a host-supported icon token for consistent navigation
|
|
371
392
|
3. **Error Handling**: Comprehensive error handling with logging
|
|
372
393
|
4. **Resource Management**: Proper cleanup of sidebar items and event listeners
|
|
373
394
|
5. **TypeScript**: Full type safety with proper imports
|
|
@@ -377,10 +398,9 @@ export default enable;
|
|
|
377
398
|
|
|
378
399
|
```typescript
|
|
379
400
|
// components/FeesPage.tsx
|
|
380
|
-
import React, { useEffect
|
|
401
|
+
import React, { useEffect } from 'react';
|
|
381
402
|
import { useQuery } from '@tanstack/react-query';
|
|
382
403
|
import type { AddonContext } from '@wealthfolio/addon-sdk';
|
|
383
|
-
import type { Holding, Account, Activity } from '@wealthfolio/addon-sdk/types';
|
|
384
404
|
|
|
385
405
|
interface FeesPageProps {
|
|
386
406
|
ctx: AddonContext;
|
|
@@ -415,25 +435,20 @@ export function FeesPage({ ctx }: FeesPageProps) {
|
|
|
415
435
|
|
|
416
436
|
// Calculate total fees from activities
|
|
417
437
|
const totalFees = React.useMemo(() => {
|
|
418
|
-
if (!activities
|
|
438
|
+
if (!activities) return 0;
|
|
419
439
|
|
|
420
|
-
return activities.
|
|
440
|
+
return activities.reduce((total, activity) => {
|
|
421
441
|
// Look for fee-related activities or transaction costs
|
|
422
|
-
const fee = activity.fee
|
|
442
|
+
const fee = Number(activity.fee ?? 0);
|
|
423
443
|
return total + fee;
|
|
424
444
|
}, 0);
|
|
425
445
|
}, [activities]);
|
|
426
446
|
|
|
427
447
|
useEffect(() => {
|
|
428
448
|
if (!isLoading) {
|
|
429
|
-
ctx.api.logger.info(
|
|
430
|
-
accountsCount: accounts?.length,
|
|
431
|
-
holdingsCount: holdings?.length,
|
|
432
|
-
activitiesCount: activities?.data?.length,
|
|
433
|
-
totalFees
|
|
434
|
-
});
|
|
449
|
+
ctx.api.logger.info("Fees data loaded successfully");
|
|
435
450
|
}
|
|
436
|
-
}, [isLoading,
|
|
451
|
+
}, [isLoading, ctx.api.logger]);
|
|
437
452
|
|
|
438
453
|
if (isLoading) {
|
|
439
454
|
return (
|
|
@@ -476,14 +491,16 @@ export function FeesPage({ ctx }: FeesPageProps) {
|
|
|
476
491
|
<div className="bg-white p-6 rounded-lg shadow border">
|
|
477
492
|
<h2 className="text-xl font-semibold mb-4">Recent Fee Activities</h2>
|
|
478
493
|
<div className="space-y-3">
|
|
479
|
-
{activities?.
|
|
494
|
+
{activities?.slice(0, 5).map((activity) => (
|
|
480
495
|
<div key={activity.id} className="flex justify-between items-center py-2 border-b">
|
|
481
496
|
<div>
|
|
482
497
|
<p className="font-medium">{activity.activityType}</p>
|
|
483
|
-
<p className="text-sm text-gray-600">
|
|
498
|
+
<p className="text-sm text-gray-600">
|
|
499
|
+
{activity.date.toLocaleDateString()}
|
|
500
|
+
</p>
|
|
484
501
|
</div>
|
|
485
502
|
<span className="text-red-600 font-medium">
|
|
486
|
-
${(activity.fee
|
|
503
|
+
${Number(activity.fee ?? 0).toFixed(2)}
|
|
487
504
|
</span>
|
|
488
505
|
</div>
|
|
489
506
|
))}
|
|
@@ -535,10 +552,13 @@ export default AnalyticsDashboard;
|
|
|
535
552
|
```typescript
|
|
536
553
|
// hooks/usePortfolioData.ts
|
|
537
554
|
import { useState, useEffect } from 'react';
|
|
538
|
-
import {
|
|
539
|
-
|
|
555
|
+
import type {
|
|
556
|
+
AddonContext,
|
|
557
|
+
Holding,
|
|
558
|
+
PerformanceResult,
|
|
559
|
+
} from '@wealthfolio/addon-sdk';
|
|
540
560
|
|
|
541
|
-
export function usePortfolioData(accountId?: string) {
|
|
561
|
+
export function usePortfolioData(ctx: AddonContext, accountId?: string) {
|
|
542
562
|
const [holdings, setHoldings] = useState<Holding[]>([]);
|
|
543
563
|
const [performance, setPerformance] = useState<PerformanceResult | null>(
|
|
544
564
|
null,
|
|
@@ -552,26 +572,25 @@ export function usePortfolioData(accountId?: string) {
|
|
|
552
572
|
setLoading(true);
|
|
553
573
|
setError(null);
|
|
554
574
|
|
|
555
|
-
|
|
575
|
+
if (!accountId) {
|
|
576
|
+
setHoldings([]);
|
|
577
|
+
setPerformance(null);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
556
580
|
|
|
557
|
-
const holdingsData = await ctx.api.portfolio.getHoldings(
|
|
558
|
-
accountId || '',
|
|
559
|
-
);
|
|
581
|
+
const holdingsData = await ctx.api.portfolio.getHoldings(accountId);
|
|
560
582
|
setHoldings(holdingsData);
|
|
561
583
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
);
|
|
573
|
-
setPerformance(performanceData);
|
|
574
|
-
}
|
|
584
|
+
const performanceData = await ctx.api.performance.calculateSummary({
|
|
585
|
+
itemType: 'account',
|
|
586
|
+
itemId: accountId,
|
|
587
|
+
});
|
|
588
|
+
console.log(
|
|
589
|
+
performanceData.returns.twr,
|
|
590
|
+
performanceData.returns.irr,
|
|
591
|
+
performanceData.risk.maxDrawdown,
|
|
592
|
+
);
|
|
593
|
+
setPerformance(performanceData);
|
|
575
594
|
} catch (err) {
|
|
576
595
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
577
596
|
} finally {
|
|
@@ -580,7 +599,7 @@ export function usePortfolioData(accountId?: string) {
|
|
|
580
599
|
}
|
|
581
600
|
|
|
582
601
|
fetchData();
|
|
583
|
-
}, [accountId]);
|
|
602
|
+
}, [accountId, ctx]);
|
|
584
603
|
|
|
585
604
|
return { holdings, performance, loading, error };
|
|
586
605
|
}
|
|
@@ -594,18 +613,28 @@ cash flows.
|
|
|
594
613
|
|
|
595
614
|
### Permission Categories
|
|
596
615
|
|
|
597
|
-
| Category
|
|
598
|
-
|
|
|
599
|
-
| `
|
|
600
|
-
| `
|
|
601
|
-
| `events`
|
|
602
|
-
| `currency`
|
|
603
|
-
| `
|
|
604
|
-
| `
|
|
605
|
-
| `
|
|
606
|
-
| `
|
|
607
|
-
| `
|
|
608
|
-
| `
|
|
616
|
+
| Category | Risk Level | Description |
|
|
617
|
+
| --------------------- | ---------- | -------------------------------------------- |
|
|
618
|
+
| `market-data` | Low | Search and synchronize market data |
|
|
619
|
+
| `quotes` | Low | Read and update quotes |
|
|
620
|
+
| `events` | Low | Listen to application events |
|
|
621
|
+
| `currency` | Low | Access exchange rates |
|
|
622
|
+
| `assets` | Medium | Read and update financial asset profiles |
|
|
623
|
+
| `performance` | Medium | Calculate portfolio performance |
|
|
624
|
+
| `spending` | Medium | View categories and manage spending rules |
|
|
625
|
+
| `financial-planning` | Medium | Manage goals and allocations |
|
|
626
|
+
| `contribution-limits` | Medium | Manage contribution limits |
|
|
627
|
+
| `files` | Medium | Open host file dialogs |
|
|
628
|
+
| `settings` | Medium | Access application configuration |
|
|
629
|
+
| `portfolio` | High | Access holdings and valuations |
|
|
630
|
+
| `activities` | High | Access and modify transaction history |
|
|
631
|
+
| `accounts` | High | Access and create accounts |
|
|
632
|
+
| `snapshots` | High | Access and modify holdings snapshots |
|
|
633
|
+
| `network` | High | Request declared external HTTPS hosts |
|
|
634
|
+
| `secrets` | High | Store and use secrets through the OS keyring |
|
|
635
|
+
|
|
636
|
+
`ui`, `navigation`, `query`, `toast`, `logger`, and `storage` are baseline
|
|
637
|
+
capabilities and must not be declared as permissions.
|
|
609
638
|
|
|
610
639
|
### Declaring Permissions
|
|
611
640
|
|
|
@@ -614,7 +643,7 @@ cash flows.
|
|
|
614
643
|
"permissions": [
|
|
615
644
|
{
|
|
616
645
|
"category": "portfolio",
|
|
617
|
-
"functions": ["getHoldings", "getHolding"
|
|
646
|
+
"functions": ["getHoldings", "getHolding"],
|
|
618
647
|
"purpose": "Display detailed portfolio analytics and performance metrics"
|
|
619
648
|
},
|
|
620
649
|
{
|
|
@@ -624,8 +653,13 @@ cash flows.
|
|
|
624
653
|
},
|
|
625
654
|
{
|
|
626
655
|
"category": "market-data",
|
|
627
|
-
"functions": ["searchTicker"
|
|
656
|
+
"functions": ["searchTicker"],
|
|
628
657
|
"purpose": "Show price charts and enable ticker search functionality"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"category": "spending",
|
|
661
|
+
"functions": ["getCategories", "saveRule"],
|
|
662
|
+
"purpose": "Save categorization rules selected by the user"
|
|
629
663
|
}
|
|
630
664
|
]
|
|
631
665
|
}
|
|
@@ -794,7 +828,6 @@ Add an item to the application sidebar.
|
|
|
794
828
|
`chart-bar`, or `calendar-dots`
|
|
795
829
|
- `config.route` (string): Navigation route
|
|
796
830
|
- `config.order` (number): Display order (optional)
|
|
797
|
-
- `config.onClick` (function): Click handler (optional)
|
|
798
831
|
|
|
799
832
|
**Returns:** `SidebarItemHandle` with `remove()` method
|
|
800
833
|
|
|
@@ -805,8 +838,10 @@ Register a new route in the application.
|
|
|
805
838
|
**Parameters:**
|
|
806
839
|
|
|
807
840
|
- `route.path` (string): Route path pattern
|
|
808
|
-
- `route.
|
|
809
|
-
|
|
841
|
+
- `route.component` (component): Preferred; the host mounts the React component
|
|
842
|
+
and passes the current `location`
|
|
843
|
+
- `route.render` (function): Legacy imperative alternative receiving
|
|
844
|
+
`{ root, location }`
|
|
810
845
|
|
|
811
846
|
#### `onDisable(callback)`
|
|
812
847
|
|
|
@@ -821,49 +856,72 @@ Register cleanup callback for addon disable.
|
|
|
821
856
|
All data access is performed through the context's `api` property:
|
|
822
857
|
|
|
823
858
|
```typescript
|
|
824
|
-
|
|
859
|
+
// Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
|
|
825
860
|
|
|
826
861
|
// Portfolio data
|
|
827
862
|
const holdings = await ctx.api.portfolio.getHoldings(accountId);
|
|
828
863
|
const accounts = await ctx.api.accounts.getAll();
|
|
829
864
|
|
|
830
865
|
// Market data
|
|
831
|
-
const
|
|
832
|
-
const
|
|
866
|
+
const symbols = await ctx.api.market.searchTicker('AAPL');
|
|
867
|
+
const quotes = await ctx.api.quotes.getHistory(assetId);
|
|
868
|
+
const profile = await ctx.api.assets.getProfile(assetId);
|
|
833
869
|
|
|
834
870
|
// Financial planning
|
|
835
871
|
const goals = await ctx.api.goals.getAll();
|
|
836
|
-
const limits = await ctx.api.
|
|
872
|
+
const limits = await ctx.api.contributionLimits.getAll();
|
|
873
|
+
|
|
874
|
+
// Historical exchange rates and spend categorization (Wealthfolio 3.8+)
|
|
875
|
+
const rates = await ctx.api.exchangeRates.getRatesForDates([
|
|
876
|
+
{ fromCurrency: 'USD', toCurrency: 'EUR', date: '2026-09-04' },
|
|
877
|
+
]);
|
|
878
|
+
const spendCategories = await ctx.api.spending.getCategories('expense');
|
|
837
879
|
|
|
838
880
|
// Settings
|
|
839
|
-
const settings = await ctx.api.
|
|
881
|
+
const settings = await ctx.api.settings.get();
|
|
840
882
|
|
|
841
883
|
// Logging and debugging
|
|
842
884
|
ctx.api.logger.info('Operation completed successfully');
|
|
843
|
-
ctx.api.logger.error(
|
|
844
|
-
ctx.api.logger.debug(
|
|
885
|
+
ctx.api.logger.error(`Error occurred: ${String(error)}`);
|
|
886
|
+
ctx.api.logger.debug(`Debug info: ${JSON.stringify(debugData)}`);
|
|
845
887
|
```
|
|
846
888
|
|
|
847
889
|
### Available API Methods
|
|
848
890
|
|
|
849
|
-
| Method
|
|
850
|
-
|
|
|
851
|
-
| `portfolio.getHoldings(accountId)`
|
|
852
|
-
| `portfolio.getHolding(accountId, assetId)`
|
|
853
|
-
| `
|
|
854
|
-
| `
|
|
855
|
-
| `accounts.
|
|
856
|
-
| `
|
|
857
|
-
| `activities.
|
|
858
|
-
| `
|
|
859
|
-
| `
|
|
860
|
-
| `
|
|
861
|
-
| `
|
|
862
|
-
| `
|
|
863
|
-
| `
|
|
864
|
-
| `
|
|
865
|
-
| `
|
|
866
|
-
| `
|
|
891
|
+
| Method | Description | Permission Required |
|
|
892
|
+
| ------------------------------------------ | ------------------------------------------- | --------------------- |
|
|
893
|
+
| `portfolio.getHoldings(accountId)` | Get portfolio holdings for an account | `portfolio` |
|
|
894
|
+
| `portfolio.getHolding(accountId, assetId)` | Get a specific holding | `portfolio` |
|
|
895
|
+
| `performance.calculateSummary(params)` | Calculate performance metrics | `performance` |
|
|
896
|
+
| `accounts.getAll()` | Get all account information | `accounts` |
|
|
897
|
+
| `accounts.create(account)` | Create an account | `accounts` |
|
|
898
|
+
| `activities.getAll(accountId?)` | Get activity history | `activities` |
|
|
899
|
+
| `activities.create(activity)` | Create an activity | `activities` |
|
|
900
|
+
| `market.searchTicker(query)` | Search for tickers | `market-data` |
|
|
901
|
+
| `assets.getProfile(assetId)` | Get a financial asset profile | `assets` |
|
|
902
|
+
| `quotes.getHistory(assetId)` | Get historical quotes | `quotes` |
|
|
903
|
+
| `exchangeRates.getRatesForDates(pairs)` | Resolve dated exchange rates | `currency` |
|
|
904
|
+
| `spending.isEnabled()` | Check whether Spending is enabled | `spending` |
|
|
905
|
+
| `spending.getCategories(kind?)` | List expense, income, or savings categories | `spending` |
|
|
906
|
+
| `spending.getRules()` | List this addon's categorization rules | `spending` |
|
|
907
|
+
| `spending.saveRule(rule)` | Create or update an addon-owned rule | `spending` |
|
|
908
|
+
| `spending.deleteRule(ruleKey)` | Delete an addon-owned rule | `spending` |
|
|
909
|
+
| `spending.rerunRules(onlyUncategorized?)` | Re-run categorization rules | `spending` |
|
|
910
|
+
| `goals.getAll()` | Get financial goals | `financial-planning` |
|
|
911
|
+
| `contributionLimits.getAll()` | Get contribution limits | `contribution-limits` |
|
|
912
|
+
| `settings.get()` | Get application settings | `settings` |
|
|
913
|
+
| `query.getClient()` | Get this addon's QueryClient | None |
|
|
914
|
+
|
|
915
|
+
The Spending and dated exchange-rate APIs require Wealthfolio 3.8 or newer. See
|
|
916
|
+
the [complete API reference](../../docs/addons/addon-api-reference.md).
|
|
917
|
+
|
|
918
|
+
### Localization
|
|
919
|
+
|
|
920
|
+
Wealthfolio 3.8 addons can register private translation bundles with
|
|
921
|
+
`registerTranslations()` and read them from React components with
|
|
922
|
+
`useAddonTranslation()`. Addons using these exports must set
|
|
923
|
+
`minWealthfolioVersion` to `3.8.0` or newer. See the
|
|
924
|
+
[localization guide](../../docs/addons/addon-localization.md).
|
|
867
925
|
|
|
868
926
|
> Tip: `activities.getAll` accepts an optional account ID string to scope
|
|
869
927
|
> results to a single account. The SDK normalizes this for both desktop (Tauri)
|
|
@@ -900,21 +958,14 @@ const response = await ctx.api.activities.search(
|
|
|
900
958
|
The SDK provides a comprehensive logging system:
|
|
901
959
|
|
|
902
960
|
```typescript
|
|
903
|
-
|
|
961
|
+
// Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
|
|
904
962
|
|
|
905
|
-
//
|
|
906
|
-
ctx.api.logger.error(
|
|
907
|
-
ctx.api.logger.warn(
|
|
963
|
+
// Each method accepts one string message.
|
|
964
|
+
ctx.api.logger.error(`Critical error occurred: ${String(error)}`);
|
|
965
|
+
ctx.api.logger.warn(`Warning message: ${String(additionalData)}`);
|
|
908
966
|
ctx.api.logger.info('Information message');
|
|
909
|
-
ctx.api.logger.debug(
|
|
910
|
-
|
|
911
|
-
// Set log level (for development)
|
|
912
|
-
ctx.api.logger.setLevel('debug');
|
|
913
|
-
|
|
914
|
-
// Check if logging level is enabled
|
|
915
|
-
if (ctx.api.logger.isLevelEnabled('debug')) {
|
|
916
|
-
ctx.api.logger.debug('Expensive debug operation', expensiveData);
|
|
917
|
-
}
|
|
967
|
+
ctx.api.logger.debug(`Debug information: ${JSON.stringify(debugObject)}`);
|
|
968
|
+
ctx.api.logger.trace('Detailed trace message');
|
|
918
969
|
```
|
|
919
970
|
|
|
920
971
|
### Addon QueryClient Integration
|
|
@@ -924,28 +975,34 @@ across that addon's route renders, not shared with the host or other addons.
|
|
|
924
975
|
Invalidate/refetch operations are mirrored to the host:
|
|
925
976
|
|
|
926
977
|
```typescript
|
|
927
|
-
|
|
928
|
-
|
|
978
|
+
import { QueryClientProvider, useQuery } from '@tanstack/react-query';
|
|
979
|
+
import type { AddonContext, QueryClient } from '@wealthfolio/addon-sdk';
|
|
929
980
|
|
|
930
981
|
// Wrap your components with QueryClientProvider
|
|
931
|
-
const MyAddonWrapper = () => {
|
|
982
|
+
const MyAddonWrapper = ({ ctx }: { ctx: AddonContext }) => {
|
|
983
|
+
const addonQueryClient = ctx.api.query.getClient() as QueryClient;
|
|
984
|
+
|
|
932
985
|
return (
|
|
933
986
|
<QueryClientProvider client={addonQueryClient}>
|
|
934
|
-
<MyAddonComponent />
|
|
987
|
+
<MyAddonComponent ctx={ctx} />
|
|
935
988
|
</QueryClientProvider>
|
|
936
989
|
);
|
|
937
990
|
};
|
|
938
991
|
|
|
939
992
|
// Use React Query hooks in your components
|
|
940
|
-
function MyAddonComponent() {
|
|
993
|
+
function MyAddonComponent({ ctx }: { ctx: AddonContext }) {
|
|
941
994
|
const { data: accounts, isLoading } = useQuery({
|
|
942
995
|
queryKey: ['accounts'],
|
|
943
996
|
queryFn: () => ctx.api.accounts.getAll()
|
|
944
997
|
});
|
|
945
998
|
|
|
999
|
+
const selectedAccountId = accounts?.[0]?.id;
|
|
946
1000
|
const { data: holdings } = useQuery({
|
|
947
1001
|
queryKey: ['holdings', selectedAccountId],
|
|
948
|
-
queryFn: () =>
|
|
1002
|
+
queryFn: () =>
|
|
1003
|
+
selectedAccountId
|
|
1004
|
+
? ctx.api.portfolio.getHoldings(selectedAccountId)
|
|
1005
|
+
: Promise.resolve([]),
|
|
949
1006
|
enabled: !!selectedAccountId
|
|
950
1007
|
});
|
|
951
1008
|
|
|
@@ -979,9 +1036,14 @@ the required development-tools upgrade.
|
|
|
979
1036
|
// Before
|
|
980
1037
|
import ctx from '@wealthfolio/addon-sdk';
|
|
981
1038
|
|
|
982
|
-
//
|
|
983
|
-
import {
|
|
984
|
-
|
|
1039
|
+
// Current SDK
|
|
1040
|
+
import type { AddonEnableFunction } from '@wealthfolio/addon-sdk';
|
|
1041
|
+
|
|
1042
|
+
const enable: AddonEnableFunction = (ctx) => {
|
|
1043
|
+
// Pass ctx to components, hooks, and helper functions that need host APIs.
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
export default enable;
|
|
985
1047
|
```
|
|
986
1048
|
|
|
987
1049
|
#### Type Imports
|
|
@@ -1208,9 +1270,9 @@ npm publish --tag beta
|
|
|
1208
1270
|
|
|
1209
1271
|
```typescript
|
|
1210
1272
|
// In your addon
|
|
1211
|
-
|
|
1212
|
-
ctx.api.logger.
|
|
1213
|
-
|
|
1273
|
+
function logDebug(ctx: AddonContext, data: unknown) {
|
|
1274
|
+
ctx.api.logger.debug(`Debug information: ${JSON.stringify(data)}`);
|
|
1275
|
+
}
|
|
1214
1276
|
```
|
|
1215
1277
|
|
|
1216
1278
|
#### 2. Development Console
|
|
@@ -1241,11 +1303,9 @@ if (process.env.NODE_ENV === 'development') {
|
|
|
1241
1303
|
#### 1. Error Handling
|
|
1242
1304
|
|
|
1243
1305
|
```typescript
|
|
1244
|
-
import {
|
|
1245
|
-
|
|
1246
|
-
async function fetchPortfolioData() {
|
|
1247
|
-
const ctx = getAddonContext();
|
|
1306
|
+
import type { AddonContext } from '@wealthfolio/addon-sdk';
|
|
1248
1307
|
|
|
1308
|
+
async function fetchPortfolioData(ctx: AddonContext) {
|
|
1249
1309
|
try {
|
|
1250
1310
|
// Get all accounts first, then holdings for each
|
|
1251
1311
|
const accounts = await ctx.api.accounts.getAll();
|
|
@@ -1254,14 +1314,8 @@ async function fetchPortfolioData() {
|
|
|
1254
1314
|
).then((results) => results.flat());
|
|
1255
1315
|
return holdings;
|
|
1256
1316
|
} catch (error) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
// Handle different error types
|
|
1260
|
-
if (error.code === 'PERMISSION_DENIED') {
|
|
1261
|
-
// Show permission error to user
|
|
1262
|
-
} else if (error.code === 'NETWORK_ERROR') {
|
|
1263
|
-
// Handle network issues
|
|
1264
|
-
}
|
|
1317
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1318
|
+
ctx.api.logger.error(`Failed to fetch holdings: ${message}`);
|
|
1265
1319
|
|
|
1266
1320
|
throw error;
|
|
1267
1321
|
}
|
|
@@ -1325,17 +1379,15 @@ const HeavyChart = lazy(() => import('./components/HeavyChart'));
|
|
|
1325
1379
|
|
|
1326
1380
|
```typescript
|
|
1327
1381
|
// Use React Query or SWR for caching
|
|
1328
|
-
import { useQuery } from 'react-query';
|
|
1382
|
+
import { useQuery } from '@tanstack/react-query';
|
|
1329
1383
|
|
|
1330
1384
|
function usePortfolioData(accountId: string) {
|
|
1331
|
-
return useQuery(
|
|
1332
|
-
['portfolio', accountId],
|
|
1333
|
-
() => ctx.api.portfolio.getHoldings(accountId),
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
},
|
|
1338
|
-
);
|
|
1385
|
+
return useQuery({
|
|
1386
|
+
queryKey: ['portfolio', accountId],
|
|
1387
|
+
queryFn: () => ctx.api.portfolio.getHoldings(accountId),
|
|
1388
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
1389
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
1390
|
+
});
|
|
1339
1391
|
}
|
|
1340
1392
|
```
|
|
1341
1393
|
|
|
@@ -1438,6 +1490,7 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
|
|
|
1438
1490
|
|
|
1439
1491
|
| SDK Version | Wealthfolio Version | Node.js | React |
|
|
1440
1492
|
| ----------- | ------------------- | --------- | ------- |
|
|
1493
|
+
| 3.8.x | >= 3.8.0 | >= 20.0.0 | ^19.2.4 |
|
|
1441
1494
|
| 3.7.x | >= 3.7.0 | >= 20.0.0 | ^19.2.4 |
|
|
1442
1495
|
| 0.9.x | >= 0.9.0 | >= 16.0.0 | ^17.0.0 |
|
|
1443
1496
|
|
|
@@ -1450,10 +1503,10 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
|
|
|
1450
1503
|
npm install @wealthfolio/addon-sdk
|
|
1451
1504
|
|
|
1452
1505
|
# Specific version
|
|
1453
|
-
npm install @wealthfolio/addon-sdk@3.
|
|
1506
|
+
npm install @wealthfolio/addon-sdk@3.8.0
|
|
1454
1507
|
|
|
1455
1508
|
# Version range
|
|
1456
|
-
npm install @wealthfolio/addon-sdk@^3.
|
|
1509
|
+
npm install @wealthfolio/addon-sdk@^3.8.0
|
|
1457
1510
|
```
|
|
1458
1511
|
|
|
1459
1512
|
#### Beta/Preview Releases
|
|
@@ -1709,31 +1762,29 @@ export default defineConfig({
|
|
|
1709
1762
|
"permissions": [
|
|
1710
1763
|
{
|
|
1711
1764
|
"category": "portfolio",
|
|
1712
|
-
"functions": ["
|
|
1765
|
+
"functions": ["getHoldings"],
|
|
1713
1766
|
"purpose": "Access portfolio data for analytics"
|
|
1714
1767
|
}
|
|
1715
1768
|
]
|
|
1716
1769
|
}
|
|
1717
1770
|
```
|
|
1718
1771
|
|
|
1719
|
-
#### 6. Context Not Available
|
|
1772
|
+
#### 6. Context Not Available in a Component or Helper
|
|
1720
1773
|
|
|
1721
|
-
**Error**:
|
|
1774
|
+
**Error**: A component or helper cannot access the addon context.
|
|
1722
1775
|
|
|
1723
1776
|
**Solutions**:
|
|
1724
1777
|
|
|
1725
1778
|
```typescript
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
const ctx = getAddonContext();
|
|
1731
|
-
// ... use context
|
|
1732
|
-
}, []);
|
|
1779
|
+
import type { AddonContext, AddonEnableFunction } from '@wealthfolio/addon-sdk';
|
|
1780
|
+
|
|
1781
|
+
function MyComponent({ ctx }: { ctx: AddonContext }) {
|
|
1782
|
+
return <button onClick={() => ctx.api.toast.success('Ready')}>Test API</button>;
|
|
1733
1783
|
}
|
|
1734
1784
|
|
|
1735
|
-
|
|
1736
|
-
//
|
|
1785
|
+
const enable: AddonEnableFunction = (ctx) => {
|
|
1786
|
+
// Capture ctx for a route wrapper, or pass it directly to helpers/components.
|
|
1787
|
+
};
|
|
1737
1788
|
```
|
|
1738
1789
|
|
|
1739
1790
|
### Development Environment Issues
|
|
@@ -1779,13 +1830,10 @@ ls -la dist/ # Should update when you save files
|
|
|
1779
1830
|
try {
|
|
1780
1831
|
const accounts = await ctx.api.accounts.getAll();
|
|
1781
1832
|
const data = await ctx.api.portfolio.getHoldings(accounts[0]?.id);
|
|
1782
|
-
ctx.api.logger.info(
|
|
1833
|
+
ctx.api.logger.info(`Data loaded successfully (${data.length} holdings)`);
|
|
1783
1834
|
} catch (error) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
stack: error.stack,
|
|
1787
|
-
timestamp: new Date().toISOString(),
|
|
1788
|
-
});
|
|
1835
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1836
|
+
ctx.api.logger.error(`API call failed: ${message}`);
|
|
1789
1837
|
}
|
|
1790
1838
|
```
|
|
1791
1839
|
|