@robosystems/client 0.2.48 → 0.3.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/extensions/InvestorClient.d.ts +91 -0
- package/extensions/InvestorClient.js +223 -0
- package/extensions/InvestorClient.ts +501 -0
- package/extensions/LedgerClient.d.ts +213 -165
- package/extensions/LedgerClient.js +315 -393
- package/extensions/LedgerClient.ts +887 -564
- package/extensions/ReportClient.d.ts +77 -115
- package/extensions/ReportClient.js +124 -211
- package/extensions/ReportClient.ts +298 -358
- package/extensions/config.js +1 -1
- package/extensions/config.ts +1 -1
- package/extensions/graphql/client.d.ts +46 -0
- package/extensions/graphql/client.js +75 -0
- package/extensions/graphql/client.ts +82 -0
- package/extensions/graphql/generated/graphql.d.ts +1786 -0
- package/extensions/graphql/generated/graphql.js +2671 -0
- package/extensions/graphql/generated/graphql.ts +4491 -0
- package/extensions/graphql/queries/investor/holdings.d.ts +8 -0
- package/extensions/graphql/queries/investor/holdings.js +36 -0
- package/extensions/graphql/queries/investor/holdings.ts +34 -0
- package/extensions/graphql/queries/investor/portfolio.d.ts +4 -0
- package/extensions/graphql/queries/investor/portfolio.js +21 -0
- package/extensions/graphql/queries/investor/portfolio.ts +19 -0
- package/extensions/graphql/queries/investor/portfolios.d.ts +4 -0
- package/extensions/graphql/queries/investor/portfolios.js +29 -0
- package/extensions/graphql/queries/investor/portfolios.ts +27 -0
- package/extensions/graphql/queries/investor/position.d.ts +4 -0
- package/extensions/graphql/queries/investor/position.js +33 -0
- package/extensions/graphql/queries/investor/position.ts +31 -0
- package/extensions/graphql/queries/investor/positions.d.ts +8 -0
- package/extensions/graphql/queries/investor/positions.js +57 -0
- package/extensions/graphql/queries/investor/positions.ts +55 -0
- package/extensions/graphql/queries/investor/securities.d.ts +8 -0
- package/extensions/graphql/queries/investor/securities.js +50 -0
- package/extensions/graphql/queries/investor/securities.ts +48 -0
- package/extensions/graphql/queries/investor/security.d.ts +4 -0
- package/extensions/graphql/queries/investor/security.js +26 -0
- package/extensions/graphql/queries/investor/security.ts +24 -0
- package/extensions/graphql/queries/ledger/accountRollups.d.ts +7 -0
- package/extensions/graphql/queries/ledger/accountRollups.js +36 -0
- package/extensions/graphql/queries/ledger/accountRollups.ts +34 -0
- package/extensions/graphql/queries/ledger/accountTree.d.ts +11 -0
- package/extensions/graphql/queries/ledger/accountTree.js +61 -0
- package/extensions/graphql/queries/ledger/accountTree.ts +59 -0
- package/extensions/graphql/queries/ledger/accounts.d.ts +8 -0
- package/extensions/graphql/queries/ledger/accounts.js +45 -0
- package/extensions/graphql/queries/ledger/accounts.ts +43 -0
- package/extensions/graphql/queries/ledger/closingBookStructures.d.ts +6 -0
- package/extensions/graphql/queries/ledger/closingBookStructures.js +27 -0
- package/extensions/graphql/queries/ledger/closingBookStructures.ts +25 -0
- package/extensions/graphql/queries/ledger/elements.d.ts +6 -0
- package/extensions/graphql/queries/ledger/elements.js +56 -0
- package/extensions/graphql/queries/ledger/elements.ts +54 -0
- package/extensions/graphql/queries/ledger/entities.d.ts +7 -0
- package/extensions/graphql/queries/ledger/entities.js +31 -0
- package/extensions/graphql/queries/ledger/entities.ts +29 -0
- package/extensions/graphql/queries/ledger/entity.d.ts +14 -0
- package/extensions/graphql/queries/ledger/entity.js +55 -0
- package/extensions/graphql/queries/ledger/entity.ts +54 -0
- package/extensions/graphql/queries/ledger/fiscalCalendar.d.ts +8 -0
- package/extensions/graphql/queries/ledger/fiscalCalendar.js +35 -0
- package/extensions/graphql/queries/ledger/fiscalCalendar.ts +33 -0
- package/extensions/graphql/queries/ledger/mappedTrialBalance.d.ts +9 -0
- package/extensions/graphql/queries/ledger/mappedTrialBalance.js +29 -0
- package/extensions/graphql/queries/ledger/mappedTrialBalance.ts +27 -0
- package/extensions/graphql/queries/ledger/mapping.d.ts +5 -0
- package/extensions/graphql/queries/ledger/mapping.js +35 -0
- package/extensions/graphql/queries/ledger/mapping.ts +33 -0
- package/extensions/graphql/queries/ledger/mappingCoverage.d.ts +5 -0
- package/extensions/graphql/queries/ledger/mappingCoverage.js +22 -0
- package/extensions/graphql/queries/ledger/mappingCoverage.ts +20 -0
- package/extensions/graphql/queries/ledger/mappings.d.ts +6 -0
- package/extensions/graphql/queries/ledger/mappings.js +23 -0
- package/extensions/graphql/queries/ledger/mappings.ts +21 -0
- package/extensions/graphql/queries/ledger/periodCloseStatus.d.ts +6 -0
- package/extensions/graphql/queries/ledger/periodCloseStatus.js +29 -0
- package/extensions/graphql/queries/ledger/periodCloseStatus.ts +27 -0
- package/extensions/graphql/queries/ledger/periodDrafts.d.ts +8 -0
- package/extensions/graphql/queries/ledger/periodDrafts.js +45 -0
- package/extensions/graphql/queries/ledger/periodDrafts.ts +43 -0
- package/extensions/graphql/queries/ledger/publishList.d.ts +5 -0
- package/extensions/graphql/queries/ledger/publishList.js +29 -0
- package/extensions/graphql/queries/ledger/publishList.ts +27 -0
- package/extensions/graphql/queries/ledger/publishLists.d.ts +5 -0
- package/extensions/graphql/queries/ledger/publishLists.js +29 -0
- package/extensions/graphql/queries/ledger/publishLists.ts +27 -0
- package/extensions/graphql/queries/ledger/report.d.ts +5 -0
- package/extensions/graphql/queries/ledger/report.js +40 -0
- package/extensions/graphql/queries/ledger/report.ts +38 -0
- package/extensions/graphql/queries/ledger/reportingTaxonomy.d.ts +6 -0
- package/extensions/graphql/queries/ledger/reportingTaxonomy.js +27 -0
- package/extensions/graphql/queries/ledger/reportingTaxonomy.ts +25 -0
- package/extensions/graphql/queries/ledger/reports.d.ts +8 -0
- package/extensions/graphql/queries/ledger/reports.js +45 -0
- package/extensions/graphql/queries/ledger/reports.ts +43 -0
- package/extensions/graphql/queries/ledger/scheduleFacts.d.ts +7 -0
- package/extensions/graphql/queries/ledger/scheduleFacts.js +24 -0
- package/extensions/graphql/queries/ledger/scheduleFacts.ts +22 -0
- package/extensions/graphql/queries/ledger/schedules.d.ts +6 -0
- package/extensions/graphql/queries/ledger/schedules.js +24 -0
- package/extensions/graphql/queries/ledger/schedules.ts +22 -0
- package/extensions/graphql/queries/ledger/statement.d.ts +9 -0
- package/extensions/graphql/queries/ledger/statement.js +43 -0
- package/extensions/graphql/queries/ledger/statement.ts +41 -0
- package/extensions/graphql/queries/ledger/structures.d.ts +5 -0
- package/extensions/graphql/queries/ledger/structures.js +22 -0
- package/extensions/graphql/queries/ledger/structures.ts +20 -0
- package/extensions/graphql/queries/ledger/summary.d.ts +7 -0
- package/extensions/graphql/queries/ledger/summary.js +25 -0
- package/extensions/graphql/queries/ledger/summary.ts +23 -0
- package/extensions/graphql/queries/ledger/taxonomies.d.ts +5 -0
- package/extensions/graphql/queries/ledger/taxonomies.js +28 -0
- package/extensions/graphql/queries/ledger/taxonomies.ts +26 -0
- package/extensions/graphql/queries/ledger/transaction.d.ts +6 -0
- package/extensions/graphql/queries/ledger/transaction.js +49 -0
- package/extensions/graphql/queries/ledger/transaction.ts +47 -0
- package/extensions/graphql/queries/ledger/transactions.d.ts +7 -0
- package/extensions/graphql/queries/ledger/transactions.js +49 -0
- package/extensions/graphql/queries/ledger/transactions.ts +47 -0
- package/extensions/graphql/queries/ledger/trialBalance.d.ts +7 -0
- package/extensions/graphql/queries/ledger/trialBalance.js +28 -0
- package/extensions/graphql/queries/ledger/trialBalance.ts +26 -0
- package/extensions/graphql/queries/ledger/unmappedElements.d.ts +8 -0
- package/extensions/graphql/queries/ledger/unmappedElements.js +29 -0
- package/extensions/graphql/queries/ledger/unmappedElements.ts +27 -0
- package/extensions/hooks.d.ts +1 -1
- package/extensions/index.d.ts +5 -1
- package/extensions/index.js +13 -1
- package/extensions/index.ts +14 -0
- package/index.ts +2 -2
- package/package.json +14 -5
- package/sdk/index.d.ts +2 -2
- package/sdk/index.js +38 -65
- package/sdk/index.ts +2 -2
- package/sdk/sdk.gen.d.ts +101 -220
- package/sdk/sdk.gen.js +251 -445
- package/sdk/sdk.gen.ts +216 -409
- package/sdk/types.gen.d.ts +4586 -7045
- package/sdk/types.gen.ts +4702 -7334
- package/sdk.gen.d.ts +101 -220
- package/sdk.gen.js +251 -445
- package/sdk.gen.ts +216 -409
- package/types.gen.d.ts +4586 -7045
- package/types.gen.ts +4702 -7334
- package/extensions/AgentClient.test.ts +0 -403
- package/extensions/LedgerClient.test.ts +0 -1138
- package/extensions/OperationClient.test.ts +0 -365
- package/extensions/QueryClient.test.ts +0 -432
- package/extensions/ReportClient.test.ts +0 -828
- package/extensions/SSEClient.test.ts +0 -375
- package/extensions/config.test.ts +0 -199
- package/extensions/hooks.test.ts +0 -373
- package/extensions/index.test.ts +0 -420
- package/sdk-extensions/AgentClient.d.ts +0 -82
- package/sdk-extensions/AgentClient.js +0 -218
- package/sdk-extensions/AgentClient.test.ts +0 -403
- package/sdk-extensions/AgentClient.ts +0 -321
- package/sdk-extensions/LedgerClient.d.ts +0 -242
- package/sdk-extensions/LedgerClient.js +0 -496
- package/sdk-extensions/LedgerClient.test.ts +0 -1138
- package/sdk-extensions/LedgerClient.ts +0 -800
- package/sdk-extensions/OperationClient.d.ts +0 -65
- package/sdk-extensions/OperationClient.js +0 -251
- package/sdk-extensions/OperationClient.test.ts +0 -365
- package/sdk-extensions/OperationClient.ts +0 -324
- package/sdk-extensions/QueryClient.d.ts +0 -52
- package/sdk-extensions/QueryClient.js +0 -313
- package/sdk-extensions/QueryClient.test.ts +0 -432
- package/sdk-extensions/QueryClient.ts +0 -420
- package/sdk-extensions/README.md +0 -901
- package/sdk-extensions/ReportClient.d.ts +0 -151
- package/sdk-extensions/ReportClient.js +0 -278
- package/sdk-extensions/ReportClient.test.ts +0 -828
- package/sdk-extensions/ReportClient.ts +0 -486
- package/sdk-extensions/SSEClient.d.ts +0 -62
- package/sdk-extensions/SSEClient.js +0 -154
- package/sdk-extensions/SSEClient.test.ts +0 -375
- package/sdk-extensions/SSEClient.ts +0 -210
- package/sdk-extensions/config.d.ts +0 -57
- package/sdk-extensions/config.js +0 -152
- package/sdk-extensions/config.test.ts +0 -199
- package/sdk-extensions/config.ts +0 -175
- package/sdk-extensions/hooks.d.ts +0 -110
- package/sdk-extensions/hooks.js +0 -384
- package/sdk-extensions/hooks.test.ts +0 -373
- package/sdk-extensions/hooks.ts +0 -459
- package/sdk-extensions/index.d.ts +0 -63
- package/sdk-extensions/index.js +0 -164
- package/sdk-extensions/index.test.ts +0 -420
- package/sdk-extensions/index.ts +0 -203
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Investor Client for RoboSystems API
|
|
5
|
+
*
|
|
6
|
+
* High-level facade for the RoboInvestor domain: portfolios, securities,
|
|
7
|
+
* positions, and portfolio holdings aggregation. Follows the same
|
|
8
|
+
* hybrid transport pattern as `LedgerClient`:
|
|
9
|
+
*
|
|
10
|
+
* - **Reads** go through GraphQL at `/extensions/{graph_id}/graphql`.
|
|
11
|
+
* - **Writes** go through named operations at
|
|
12
|
+
* `/extensions/roboinvestor/{graph_id}/operations/{operation_name}`.
|
|
13
|
+
*
|
|
14
|
+
* Every write returns an `OperationEnvelope`; this facade unwraps
|
|
15
|
+
* `envelope.result` and returns a typed shape derived from the GraphQL
|
|
16
|
+
* codegen output (which is the source of truth for read payload types).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
|
|
20
|
+
import { ClientError } from 'graphql-request'
|
|
21
|
+
import {
|
|
22
|
+
opCreatePortfolio,
|
|
23
|
+
opCreatePosition,
|
|
24
|
+
opCreateSecurity,
|
|
25
|
+
opDeletePortfolio,
|
|
26
|
+
opDeletePosition,
|
|
27
|
+
opDeleteSecurity,
|
|
28
|
+
opUpdatePortfolio,
|
|
29
|
+
opUpdatePosition,
|
|
30
|
+
opUpdateSecurity,
|
|
31
|
+
} from '../sdk.gen'
|
|
32
|
+
import type {
|
|
33
|
+
CreatePortfolioRequest,
|
|
34
|
+
CreatePositionRequest,
|
|
35
|
+
CreateSecurityRequest,
|
|
36
|
+
OperationEnvelope,
|
|
37
|
+
UpdatePortfolioOperation,
|
|
38
|
+
UpdatePositionOperation,
|
|
39
|
+
UpdateSecurityOperation,
|
|
40
|
+
} from '../types.gen'
|
|
41
|
+
import { GraphQLClientCache } from './graphql/client'
|
|
42
|
+
import {
|
|
43
|
+
GetInvestorHoldingsDocument,
|
|
44
|
+
GetInvestorPortfolioDocument,
|
|
45
|
+
GetInvestorPositionDocument,
|
|
46
|
+
GetInvestorSecurityDocument,
|
|
47
|
+
ListInvestorPortfoliosDocument,
|
|
48
|
+
ListInvestorPositionsDocument,
|
|
49
|
+
ListInvestorSecuritiesDocument,
|
|
50
|
+
type GetInvestorHoldingsQuery,
|
|
51
|
+
type GetInvestorPortfolioQuery,
|
|
52
|
+
type GetInvestorPositionQuery,
|
|
53
|
+
type GetInvestorSecurityQuery,
|
|
54
|
+
type ListInvestorPortfoliosQuery,
|
|
55
|
+
type ListInvestorPositionsQuery,
|
|
56
|
+
type ListInvestorSecuritiesQuery,
|
|
57
|
+
} from './graphql/generated/graphql'
|
|
58
|
+
|
|
59
|
+
// ── Friendly types derived from GraphQL codegen ────────────────────────
|
|
60
|
+
|
|
61
|
+
export type InvestorPortfolioList = NonNullable<ListInvestorPortfoliosQuery['portfolios']>
|
|
62
|
+
export type InvestorPortfolioSummary = InvestorPortfolioList['portfolios'][number]
|
|
63
|
+
export type InvestorPortfolio = NonNullable<GetInvestorPortfolioQuery['portfolio']>
|
|
64
|
+
|
|
65
|
+
export type InvestorSecurityList = NonNullable<ListInvestorSecuritiesQuery['securities']>
|
|
66
|
+
export type InvestorSecuritySummary = InvestorSecurityList['securities'][number]
|
|
67
|
+
export type InvestorSecurity = NonNullable<GetInvestorSecurityQuery['security']>
|
|
68
|
+
|
|
69
|
+
export type InvestorPositionList = NonNullable<ListInvestorPositionsQuery['positions']>
|
|
70
|
+
export type InvestorPositionSummary = InvestorPositionList['positions'][number]
|
|
71
|
+
export type InvestorPosition = NonNullable<GetInvestorPositionQuery['position']>
|
|
72
|
+
|
|
73
|
+
export type InvestorHoldings = NonNullable<GetInvestorHoldingsQuery['holdings']>
|
|
74
|
+
export type InvestorEntityHolding = InvestorHoldings['holdings'][number]
|
|
75
|
+
export type InvestorHeldSecurity = InvestorEntityHolding['securities'][number]
|
|
76
|
+
|
|
77
|
+
// ── Client ──────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
interface InvestorClientConfig {
|
|
80
|
+
baseUrl: string
|
|
81
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
82
|
+
headers?: Record<string, string>
|
|
83
|
+
token?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class InvestorClient {
|
|
87
|
+
private config: InvestorClientConfig
|
|
88
|
+
private gql: GraphQLClientCache
|
|
89
|
+
|
|
90
|
+
constructor(config: InvestorClientConfig) {
|
|
91
|
+
this.config = config
|
|
92
|
+
this.gql = new GraphQLClientCache(config)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Portfolios ──────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/** List portfolios with pagination. */
|
|
98
|
+
async listPortfolios(
|
|
99
|
+
graphId: string,
|
|
100
|
+
options?: { limit?: number; offset?: number }
|
|
101
|
+
): Promise<InvestorPortfolioList | null> {
|
|
102
|
+
return this.gqlQuery(
|
|
103
|
+
graphId,
|
|
104
|
+
ListInvestorPortfoliosDocument,
|
|
105
|
+
{
|
|
106
|
+
limit: options?.limit ?? 100,
|
|
107
|
+
offset: options?.offset ?? 0,
|
|
108
|
+
},
|
|
109
|
+
'List portfolios',
|
|
110
|
+
(data) => data.portfolios
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Get a single portfolio by id. Returns null if it doesn't exist. */
|
|
115
|
+
async getPortfolio(graphId: string, portfolioId: string): Promise<InvestorPortfolio | null> {
|
|
116
|
+
return this.gqlQuery(
|
|
117
|
+
graphId,
|
|
118
|
+
GetInvestorPortfolioDocument,
|
|
119
|
+
{ portfolioId },
|
|
120
|
+
'Get portfolio',
|
|
121
|
+
(data) => data.portfolio
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Create a new portfolio. Returns the created portfolio. */
|
|
126
|
+
async createPortfolio(graphId: string, body: CreatePortfolioRequest): Promise<InvestorPortfolio> {
|
|
127
|
+
const envelope = await this.callOperation(
|
|
128
|
+
'Create portfolio',
|
|
129
|
+
opCreatePortfolio({ path: { graph_id: graphId }, body })
|
|
130
|
+
)
|
|
131
|
+
return rawToInvestorPortfolio(envelope.result as unknown as RawPortfolioResponse)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Update a portfolio's metadata. Only provided fields are applied. */
|
|
135
|
+
async updatePortfolio(
|
|
136
|
+
graphId: string,
|
|
137
|
+
portfolioId: string,
|
|
138
|
+
updates: Omit<UpdatePortfolioOperation, 'portfolio_id'>
|
|
139
|
+
): Promise<InvestorPortfolio> {
|
|
140
|
+
const envelope = await this.callOperation(
|
|
141
|
+
'Update portfolio',
|
|
142
|
+
opUpdatePortfolio({
|
|
143
|
+
path: { graph_id: graphId },
|
|
144
|
+
body: {
|
|
145
|
+
...updates,
|
|
146
|
+
portfolio_id: portfolioId,
|
|
147
|
+
} as UpdatePortfolioOperation,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
return rawToInvestorPortfolio(envelope.result as unknown as RawPortfolioResponse)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Delete a portfolio. Fails with 409 if the portfolio still has active
|
|
155
|
+
* positions — dispose those first.
|
|
156
|
+
*/
|
|
157
|
+
async deletePortfolio(graphId: string, portfolioId: string): Promise<{ deleted: boolean }> {
|
|
158
|
+
const envelope = await this.callOperation(
|
|
159
|
+
'Delete portfolio',
|
|
160
|
+
opDeletePortfolio({
|
|
161
|
+
path: { graph_id: graphId },
|
|
162
|
+
body: { portfolio_id: portfolioId },
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
return (envelope.result ?? { deleted: true }) as { deleted: boolean }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Securities ──────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/** List securities with pagination and filters. */
|
|
171
|
+
async listSecurities(
|
|
172
|
+
graphId: string,
|
|
173
|
+
options?: {
|
|
174
|
+
entityId?: string
|
|
175
|
+
securityType?: string
|
|
176
|
+
isActive?: boolean
|
|
177
|
+
limit?: number
|
|
178
|
+
offset?: number
|
|
179
|
+
}
|
|
180
|
+
): Promise<InvestorSecurityList | null> {
|
|
181
|
+
return this.gqlQuery(
|
|
182
|
+
graphId,
|
|
183
|
+
ListInvestorSecuritiesDocument,
|
|
184
|
+
{
|
|
185
|
+
entityId: options?.entityId ?? null,
|
|
186
|
+
securityType: options?.securityType ?? null,
|
|
187
|
+
isActive: options?.isActive ?? null,
|
|
188
|
+
limit: options?.limit ?? 100,
|
|
189
|
+
offset: options?.offset ?? 0,
|
|
190
|
+
},
|
|
191
|
+
'List securities',
|
|
192
|
+
(data) => data.securities
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Get a single security by id. Returns null if it doesn't exist. */
|
|
197
|
+
async getSecurity(graphId: string, securityId: string): Promise<InvestorSecurity | null> {
|
|
198
|
+
return this.gqlQuery(
|
|
199
|
+
graphId,
|
|
200
|
+
GetInvestorSecurityDocument,
|
|
201
|
+
{ securityId },
|
|
202
|
+
'Get security',
|
|
203
|
+
(data) => data.security
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create a new security. Passing `source_graph_id` auto-links to the
|
|
209
|
+
* matching entity in the target graph.
|
|
210
|
+
*/
|
|
211
|
+
async createSecurity(graphId: string, body: CreateSecurityRequest): Promise<InvestorSecurity> {
|
|
212
|
+
const envelope = await this.callOperation(
|
|
213
|
+
'Create security',
|
|
214
|
+
opCreateSecurity({ path: { graph_id: graphId }, body })
|
|
215
|
+
)
|
|
216
|
+
return rawToInvestorSecurity(envelope.result as unknown as RawSecurityResponse)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Update a security's metadata. Only provided fields are applied. */
|
|
220
|
+
async updateSecurity(
|
|
221
|
+
graphId: string,
|
|
222
|
+
securityId: string,
|
|
223
|
+
updates: Omit<UpdateSecurityOperation, 'security_id'>
|
|
224
|
+
): Promise<InvestorSecurity> {
|
|
225
|
+
const envelope = await this.callOperation(
|
|
226
|
+
'Update security',
|
|
227
|
+
opUpdateSecurity({
|
|
228
|
+
path: { graph_id: graphId },
|
|
229
|
+
body: {
|
|
230
|
+
...updates,
|
|
231
|
+
security_id: securityId,
|
|
232
|
+
} as UpdateSecurityOperation,
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
return rawToInvestorSecurity(envelope.result as unknown as RawSecurityResponse)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Soft-delete a security (sets is_active=false). */
|
|
239
|
+
async deleteSecurity(graphId: string, securityId: string): Promise<{ deleted: boolean }> {
|
|
240
|
+
const envelope = await this.callOperation(
|
|
241
|
+
'Delete security',
|
|
242
|
+
opDeleteSecurity({
|
|
243
|
+
path: { graph_id: graphId },
|
|
244
|
+
body: { security_id: securityId },
|
|
245
|
+
})
|
|
246
|
+
)
|
|
247
|
+
return (envelope.result ?? { deleted: true }) as { deleted: boolean }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Positions ───────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/** List positions with pagination and filters. */
|
|
253
|
+
async listPositions(
|
|
254
|
+
graphId: string,
|
|
255
|
+
options?: {
|
|
256
|
+
portfolioId?: string
|
|
257
|
+
securityId?: string
|
|
258
|
+
status?: string
|
|
259
|
+
limit?: number
|
|
260
|
+
offset?: number
|
|
261
|
+
}
|
|
262
|
+
): Promise<InvestorPositionList | null> {
|
|
263
|
+
return this.gqlQuery(
|
|
264
|
+
graphId,
|
|
265
|
+
ListInvestorPositionsDocument,
|
|
266
|
+
{
|
|
267
|
+
portfolioId: options?.portfolioId ?? null,
|
|
268
|
+
securityId: options?.securityId ?? null,
|
|
269
|
+
status: options?.status ?? null,
|
|
270
|
+
limit: options?.limit ?? 100,
|
|
271
|
+
offset: options?.offset ?? 0,
|
|
272
|
+
},
|
|
273
|
+
'List positions',
|
|
274
|
+
(data) => data.positions
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Get a single position by id. Returns null if it doesn't exist. */
|
|
279
|
+
async getPosition(graphId: string, positionId: string): Promise<InvestorPosition | null> {
|
|
280
|
+
return this.gqlQuery(
|
|
281
|
+
graphId,
|
|
282
|
+
GetInvestorPositionDocument,
|
|
283
|
+
{ positionId },
|
|
284
|
+
'Get position',
|
|
285
|
+
(data) => data.position
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Create a new position. */
|
|
290
|
+
async createPosition(graphId: string, body: CreatePositionRequest): Promise<InvestorPosition> {
|
|
291
|
+
const envelope = await this.callOperation(
|
|
292
|
+
'Create position',
|
|
293
|
+
opCreatePosition({ path: { graph_id: graphId }, body })
|
|
294
|
+
)
|
|
295
|
+
return rawToInvestorPosition(envelope.result as unknown as RawPositionResponse)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Update a position. Only provided fields are applied. */
|
|
299
|
+
async updatePosition(
|
|
300
|
+
graphId: string,
|
|
301
|
+
positionId: string,
|
|
302
|
+
updates: Omit<UpdatePositionOperation, 'position_id'>
|
|
303
|
+
): Promise<InvestorPosition> {
|
|
304
|
+
const envelope = await this.callOperation(
|
|
305
|
+
'Update position',
|
|
306
|
+
opUpdatePosition({
|
|
307
|
+
path: { graph_id: graphId },
|
|
308
|
+
body: {
|
|
309
|
+
...updates,
|
|
310
|
+
position_id: positionId,
|
|
311
|
+
} as UpdatePositionOperation,
|
|
312
|
+
})
|
|
313
|
+
)
|
|
314
|
+
return rawToInvestorPosition(envelope.result as unknown as RawPositionResponse)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Delete (dispose) a position. */
|
|
318
|
+
async deletePosition(graphId: string, positionId: string): Promise<{ deleted: boolean }> {
|
|
319
|
+
const envelope = await this.callOperation(
|
|
320
|
+
'Delete position',
|
|
321
|
+
opDeletePosition({
|
|
322
|
+
path: { graph_id: graphId },
|
|
323
|
+
body: { position_id: positionId },
|
|
324
|
+
})
|
|
325
|
+
)
|
|
326
|
+
return (envelope.result ?? { deleted: true }) as { deleted: boolean }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── Holdings (aggregation) ─────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get portfolio holdings grouped by entity. Returns the full list of
|
|
333
|
+
* securities held and aggregate cost-basis / current-value for each
|
|
334
|
+
* entity in the portfolio.
|
|
335
|
+
*/
|
|
336
|
+
async getHoldings(graphId: string, portfolioId: string): Promise<InvestorHoldings | null> {
|
|
337
|
+
return this.gqlQuery(
|
|
338
|
+
graphId,
|
|
339
|
+
GetInvestorHoldingsDocument,
|
|
340
|
+
{ portfolioId },
|
|
341
|
+
'Get holdings',
|
|
342
|
+
(data) => data.holdings
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Internal helpers ────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
private async gqlQuery<TData, TVars extends object, TResult>(
|
|
349
|
+
graphId: string,
|
|
350
|
+
document: TypedDocumentNode<TData, TVars>,
|
|
351
|
+
variables: TVars | undefined,
|
|
352
|
+
label: string,
|
|
353
|
+
pick: (data: TData) => TResult
|
|
354
|
+
): Promise<TResult> {
|
|
355
|
+
try {
|
|
356
|
+
const client = this.gql.get(graphId)
|
|
357
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
358
|
+
const raw = client.request as (doc: unknown, vars?: unknown) => Promise<any>
|
|
359
|
+
const data = (await raw.call(client, document, variables)) as TData
|
|
360
|
+
return pick(data)
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (err instanceof ClientError) {
|
|
363
|
+
throw new Error(`${label} failed: ${JSON.stringify(err.response.errors ?? err.message)}`)
|
|
364
|
+
}
|
|
365
|
+
throw err
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private async callOperation(
|
|
370
|
+
label: string,
|
|
371
|
+
call: Promise<{ data?: OperationEnvelope; error?: unknown }>
|
|
372
|
+
): Promise<OperationEnvelope> {
|
|
373
|
+
const response = await call
|
|
374
|
+
if (response.error !== undefined) {
|
|
375
|
+
throw new Error(`${label} failed: ${JSON.stringify(response.error)}`)
|
|
376
|
+
}
|
|
377
|
+
if (response.data === undefined) {
|
|
378
|
+
throw new Error(`${label} failed: empty response`)
|
|
379
|
+
}
|
|
380
|
+
return response.data
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ── Module-private conversion helpers ─────────────────────────────────
|
|
385
|
+
//
|
|
386
|
+
// The RoboInvestor write operations (create/update portfolio, security,
|
|
387
|
+
// position) return Pydantic-serialized envelopes where `result` is in
|
|
388
|
+
// snake_case. The GraphQL-derived `InvestorPortfolio` / `InvestorSecurity`
|
|
389
|
+
// / `InvestorPosition` types are in camelCase (matching the read path).
|
|
390
|
+
//
|
|
391
|
+
// These helpers are the single place where we bridge the two naming
|
|
392
|
+
// conventions on the write path, mirroring the `rawFiscalCalendarToCamel`
|
|
393
|
+
// / `rawToClosingEntry` pattern in `LedgerClient`. Keeping the unsafe
|
|
394
|
+
// `as unknown as` cast localized to these helpers means a backend schema
|
|
395
|
+
// drift surfaces here rather than silently at every call site.
|
|
396
|
+
|
|
397
|
+
interface RawPortfolioResponse {
|
|
398
|
+
id: string
|
|
399
|
+
name: string
|
|
400
|
+
description: string | null
|
|
401
|
+
strategy: string | null
|
|
402
|
+
inception_date: string | null
|
|
403
|
+
base_currency: string
|
|
404
|
+
created_at: string
|
|
405
|
+
updated_at: string
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
interface RawSecurityResponse {
|
|
409
|
+
id: string
|
|
410
|
+
entity_id: string | null
|
|
411
|
+
entity_name: string | null
|
|
412
|
+
source_graph_id: string | null
|
|
413
|
+
name: string
|
|
414
|
+
security_type: string
|
|
415
|
+
security_subtype: string | null
|
|
416
|
+
terms: unknown
|
|
417
|
+
is_active: boolean
|
|
418
|
+
authorized_shares: number | null
|
|
419
|
+
outstanding_shares: number | null
|
|
420
|
+
created_at: string
|
|
421
|
+
updated_at: string
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
interface RawPositionResponse {
|
|
425
|
+
id: string
|
|
426
|
+
portfolio_id: string
|
|
427
|
+
security_id: string
|
|
428
|
+
security_name: string | null
|
|
429
|
+
entity_name: string | null
|
|
430
|
+
quantity: number
|
|
431
|
+
quantity_type: string
|
|
432
|
+
cost_basis: number
|
|
433
|
+
cost_basis_dollars: number
|
|
434
|
+
currency: string
|
|
435
|
+
current_value: number | null
|
|
436
|
+
current_value_dollars: number | null
|
|
437
|
+
valuation_date: string | null
|
|
438
|
+
valuation_source: string | null
|
|
439
|
+
acquisition_date: string | null
|
|
440
|
+
disposition_date: string | null
|
|
441
|
+
status: string
|
|
442
|
+
notes: string | null
|
|
443
|
+
created_at: string
|
|
444
|
+
updated_at: string
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function rawToInvestorPortfolio(raw: RawPortfolioResponse): InvestorPortfolio {
|
|
448
|
+
return {
|
|
449
|
+
id: raw.id,
|
|
450
|
+
name: raw.name,
|
|
451
|
+
description: raw.description,
|
|
452
|
+
strategy: raw.strategy,
|
|
453
|
+
inceptionDate: raw.inception_date,
|
|
454
|
+
baseCurrency: raw.base_currency,
|
|
455
|
+
createdAt: raw.created_at,
|
|
456
|
+
updatedAt: raw.updated_at,
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function rawToInvestorSecurity(raw: RawSecurityResponse): InvestorSecurity {
|
|
461
|
+
return {
|
|
462
|
+
id: raw.id,
|
|
463
|
+
entityId: raw.entity_id,
|
|
464
|
+
entityName: raw.entity_name,
|
|
465
|
+
sourceGraphId: raw.source_graph_id,
|
|
466
|
+
name: raw.name,
|
|
467
|
+
securityType: raw.security_type,
|
|
468
|
+
securitySubtype: raw.security_subtype,
|
|
469
|
+
terms: raw.terms,
|
|
470
|
+
isActive: raw.is_active,
|
|
471
|
+
authorizedShares: raw.authorized_shares,
|
|
472
|
+
outstandingShares: raw.outstanding_shares,
|
|
473
|
+
createdAt: raw.created_at,
|
|
474
|
+
updatedAt: raw.updated_at,
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function rawToInvestorPosition(raw: RawPositionResponse): InvestorPosition {
|
|
479
|
+
return {
|
|
480
|
+
id: raw.id,
|
|
481
|
+
portfolioId: raw.portfolio_id,
|
|
482
|
+
securityId: raw.security_id,
|
|
483
|
+
securityName: raw.security_name,
|
|
484
|
+
entityName: raw.entity_name,
|
|
485
|
+
quantity: raw.quantity,
|
|
486
|
+
quantityType: raw.quantity_type,
|
|
487
|
+
costBasis: raw.cost_basis,
|
|
488
|
+
costBasisDollars: raw.cost_basis_dollars,
|
|
489
|
+
currency: raw.currency,
|
|
490
|
+
currentValue: raw.current_value,
|
|
491
|
+
currentValueDollars: raw.current_value_dollars,
|
|
492
|
+
valuationDate: raw.valuation_date,
|
|
493
|
+
valuationSource: raw.valuation_source,
|
|
494
|
+
acquisitionDate: raw.acquisition_date,
|
|
495
|
+
dispositionDate: raw.disposition_date,
|
|
496
|
+
status: raw.status,
|
|
497
|
+
notes: raw.notes,
|
|
498
|
+
createdAt: raw.created_at,
|
|
499
|
+
updatedAt: raw.updated_at,
|
|
500
|
+
}
|
|
501
|
+
}
|