@v3finance/core-sdk 0.1.0 → 0.2.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.
Files changed (217) hide show
  1. package/CHANGELOG.md +397 -7
  2. package/README.md +215 -72
  3. package/dist/cjs/client.d.ts +67 -22
  4. package/dist/cjs/client.d.ts.map +1 -1
  5. package/dist/cjs/client.js +38 -2
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/core/errors.d.ts +30 -10
  8. package/dist/cjs/core/errors.d.ts.map +1 -1
  9. package/dist/cjs/core/errors.js +47 -15
  10. package/dist/cjs/core/errors.js.map +1 -1
  11. package/dist/cjs/core/gated.d.ts +21 -3
  12. package/dist/cjs/core/gated.d.ts.map +1 -1
  13. package/dist/cjs/core/gated.js +12 -3
  14. package/dist/cjs/core/gated.js.map +1 -1
  15. package/dist/cjs/core/http.d.ts +12 -8
  16. package/dist/cjs/core/http.d.ts.map +1 -1
  17. package/dist/cjs/core/http.js +130 -40
  18. package/dist/cjs/core/http.js.map +1 -1
  19. package/dist/cjs/core/pagination.d.ts +28 -1
  20. package/dist/cjs/core/pagination.d.ts.map +1 -1
  21. package/dist/cjs/core/pagination.js +36 -1
  22. package/dist/cjs/core/pagination.js.map +1 -1
  23. package/dist/cjs/generated/operations.js +1 -1
  24. package/dist/cjs/generated/types.d.ts +948 -172
  25. package/dist/cjs/generated/types.d.ts.map +1 -1
  26. package/dist/cjs/index.d.ts +32 -8
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +12 -0
  29. package/dist/cjs/index.js.map +1 -1
  30. package/dist/cjs/resources/addresses.d.ts +22 -8
  31. package/dist/cjs/resources/addresses.d.ts.map +1 -1
  32. package/dist/cjs/resources/addresses.js +7 -1
  33. package/dist/cjs/resources/addresses.js.map +1 -1
  34. package/dist/cjs/resources/admin.d.ts +150 -25
  35. package/dist/cjs/resources/admin.d.ts.map +1 -1
  36. package/dist/cjs/resources/admin.js +94 -23
  37. package/dist/cjs/resources/admin.js.map +1 -1
  38. package/dist/cjs/resources/approvals.d.ts +46 -5
  39. package/dist/cjs/resources/approvals.d.ts.map +1 -1
  40. package/dist/cjs/resources/approvals.js +27 -4
  41. package/dist/cjs/resources/approvals.js.map +1 -1
  42. package/dist/cjs/resources/balances.d.ts +152 -16
  43. package/dist/cjs/resources/balances.d.ts.map +1 -1
  44. package/dist/cjs/resources/balances.js +9 -5
  45. package/dist/cjs/resources/balances.js.map +1 -1
  46. package/dist/cjs/resources/categories.d.ts +93 -21
  47. package/dist/cjs/resources/categories.d.ts.map +1 -1
  48. package/dist/cjs/resources/categories.js +65 -18
  49. package/dist/cjs/resources/categories.js.map +1 -1
  50. package/dist/cjs/resources/counterparties.d.ts +26 -3
  51. package/dist/cjs/resources/counterparties.d.ts.map +1 -1
  52. package/dist/cjs/resources/counterparties.js +17 -0
  53. package/dist/cjs/resources/counterparties.js.map +1 -1
  54. package/dist/cjs/resources/folders.d.ts +51 -10
  55. package/dist/cjs/resources/folders.d.ts.map +1 -1
  56. package/dist/cjs/resources/folders.js +36 -5
  57. package/dist/cjs/resources/folders.js.map +1 -1
  58. package/dist/cjs/resources/history.d.ts +20 -2
  59. package/dist/cjs/resources/history.d.ts.map +1 -1
  60. package/dist/cjs/resources/history.js.map +1 -1
  61. package/dist/cjs/resources/me.d.ts +27 -4
  62. package/dist/cjs/resources/me.d.ts.map +1 -1
  63. package/dist/cjs/resources/me.js +22 -1
  64. package/dist/cjs/resources/me.js.map +1 -1
  65. package/dist/cjs/resources/members.d.ts +19 -4
  66. package/dist/cjs/resources/members.d.ts.map +1 -1
  67. package/dist/cjs/resources/members.js +15 -6
  68. package/dist/cjs/resources/members.js.map +1 -1
  69. package/dist/cjs/resources/operations.d.ts +64 -8
  70. package/dist/cjs/resources/operations.d.ts.map +1 -1
  71. package/dist/cjs/resources/operations.js +26 -2
  72. package/dist/cjs/resources/operations.js.map +1 -1
  73. package/dist/cjs/resources/registries.d.ts +87 -9
  74. package/dist/cjs/resources/registries.d.ts.map +1 -1
  75. package/dist/cjs/resources/registries.js +54 -11
  76. package/dist/cjs/resources/registries.js.map +1 -1
  77. package/dist/cjs/resources/reports.d.ts +300 -41
  78. package/dist/cjs/resources/reports.d.ts.map +1 -1
  79. package/dist/cjs/resources/reports.js +135 -7
  80. package/dist/cjs/resources/reports.js.map +1 -1
  81. package/dist/cjs/resources/sharing.d.ts +61 -10
  82. package/dist/cjs/resources/sharing.d.ts.map +1 -1
  83. package/dist/cjs/resources/sharing.js +51 -4
  84. package/dist/cjs/resources/sharing.js.map +1 -1
  85. package/dist/cjs/resources/transfers.d.ts +26 -4
  86. package/dist/cjs/resources/transfers.d.ts.map +1 -1
  87. package/dist/cjs/resources/transfers.js +23 -1
  88. package/dist/cjs/resources/transfers.js.map +1 -1
  89. package/dist/cjs/resources/types.d.ts +20 -0
  90. package/dist/cjs/resources/types.d.ts.map +1 -1
  91. package/dist/cjs/units.d.ts.map +1 -1
  92. package/dist/cjs/units.js +18 -2
  93. package/dist/cjs/units.js.map +1 -1
  94. package/dist/cjs/version.d.ts +1 -1
  95. package/dist/cjs/version.js +1 -1
  96. package/dist/esm/client.d.ts +67 -22
  97. package/dist/esm/client.d.ts.map +1 -1
  98. package/dist/esm/client.js +38 -2
  99. package/dist/esm/client.js.map +1 -1
  100. package/dist/esm/core/errors.d.ts +30 -10
  101. package/dist/esm/core/errors.d.ts.map +1 -1
  102. package/dist/esm/core/errors.js +47 -15
  103. package/dist/esm/core/errors.js.map +1 -1
  104. package/dist/esm/core/gated.d.ts +21 -3
  105. package/dist/esm/core/gated.d.ts.map +1 -1
  106. package/dist/esm/core/gated.js +12 -3
  107. package/dist/esm/core/gated.js.map +1 -1
  108. package/dist/esm/core/http.d.ts +12 -8
  109. package/dist/esm/core/http.d.ts.map +1 -1
  110. package/dist/esm/core/http.js +131 -41
  111. package/dist/esm/core/http.js.map +1 -1
  112. package/dist/esm/core/pagination.d.ts +28 -1
  113. package/dist/esm/core/pagination.d.ts.map +1 -1
  114. package/dist/esm/core/pagination.js +36 -1
  115. package/dist/esm/core/pagination.js.map +1 -1
  116. package/dist/esm/generated/operations.js +1 -1
  117. package/dist/esm/generated/types.d.ts +948 -172
  118. package/dist/esm/generated/types.d.ts.map +1 -1
  119. package/dist/esm/index.d.ts +32 -8
  120. package/dist/esm/index.d.ts.map +1 -1
  121. package/dist/esm/index.js +17 -5
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/resources/addresses.d.ts +22 -8
  124. package/dist/esm/resources/addresses.d.ts.map +1 -1
  125. package/dist/esm/resources/addresses.js +7 -1
  126. package/dist/esm/resources/addresses.js.map +1 -1
  127. package/dist/esm/resources/admin.d.ts +150 -25
  128. package/dist/esm/resources/admin.d.ts.map +1 -1
  129. package/dist/esm/resources/admin.js +94 -23
  130. package/dist/esm/resources/admin.js.map +1 -1
  131. package/dist/esm/resources/approvals.d.ts +46 -5
  132. package/dist/esm/resources/approvals.d.ts.map +1 -1
  133. package/dist/esm/resources/approvals.js +27 -4
  134. package/dist/esm/resources/approvals.js.map +1 -1
  135. package/dist/esm/resources/balances.d.ts +152 -16
  136. package/dist/esm/resources/balances.d.ts.map +1 -1
  137. package/dist/esm/resources/balances.js +9 -5
  138. package/dist/esm/resources/balances.js.map +1 -1
  139. package/dist/esm/resources/categories.d.ts +93 -21
  140. package/dist/esm/resources/categories.d.ts.map +1 -1
  141. package/dist/esm/resources/categories.js +65 -18
  142. package/dist/esm/resources/categories.js.map +1 -1
  143. package/dist/esm/resources/counterparties.d.ts +26 -3
  144. package/dist/esm/resources/counterparties.d.ts.map +1 -1
  145. package/dist/esm/resources/counterparties.js +17 -0
  146. package/dist/esm/resources/counterparties.js.map +1 -1
  147. package/dist/esm/resources/folders.d.ts +51 -10
  148. package/dist/esm/resources/folders.d.ts.map +1 -1
  149. package/dist/esm/resources/folders.js +36 -5
  150. package/dist/esm/resources/folders.js.map +1 -1
  151. package/dist/esm/resources/history.d.ts +20 -2
  152. package/dist/esm/resources/history.d.ts.map +1 -1
  153. package/dist/esm/resources/history.js.map +1 -1
  154. package/dist/esm/resources/me.d.ts +27 -4
  155. package/dist/esm/resources/me.d.ts.map +1 -1
  156. package/dist/esm/resources/me.js +22 -1
  157. package/dist/esm/resources/me.js.map +1 -1
  158. package/dist/esm/resources/members.d.ts +19 -4
  159. package/dist/esm/resources/members.d.ts.map +1 -1
  160. package/dist/esm/resources/members.js +15 -6
  161. package/dist/esm/resources/members.js.map +1 -1
  162. package/dist/esm/resources/operations.d.ts +64 -8
  163. package/dist/esm/resources/operations.d.ts.map +1 -1
  164. package/dist/esm/resources/operations.js +26 -2
  165. package/dist/esm/resources/operations.js.map +1 -1
  166. package/dist/esm/resources/registries.d.ts +87 -9
  167. package/dist/esm/resources/registries.d.ts.map +1 -1
  168. package/dist/esm/resources/registries.js +54 -11
  169. package/dist/esm/resources/registries.js.map +1 -1
  170. package/dist/esm/resources/reports.d.ts +300 -41
  171. package/dist/esm/resources/reports.d.ts.map +1 -1
  172. package/dist/esm/resources/reports.js +135 -7
  173. package/dist/esm/resources/reports.js.map +1 -1
  174. package/dist/esm/resources/sharing.d.ts +61 -10
  175. package/dist/esm/resources/sharing.d.ts.map +1 -1
  176. package/dist/esm/resources/sharing.js +51 -4
  177. package/dist/esm/resources/sharing.js.map +1 -1
  178. package/dist/esm/resources/transfers.d.ts +26 -4
  179. package/dist/esm/resources/transfers.d.ts.map +1 -1
  180. package/dist/esm/resources/transfers.js +23 -1
  181. package/dist/esm/resources/transfers.js.map +1 -1
  182. package/dist/esm/resources/types.d.ts +20 -0
  183. package/dist/esm/resources/types.d.ts.map +1 -1
  184. package/dist/esm/units.d.ts.map +1 -1
  185. package/dist/esm/units.js +18 -2
  186. package/dist/esm/units.js.map +1 -1
  187. package/dist/esm/version.d.ts +1 -1
  188. package/dist/esm/version.js +1 -1
  189. package/docs/concepts.md +191 -0
  190. package/docs/errors.md +471 -0
  191. package/package.json +7 -6
  192. package/src/client.ts +89 -25
  193. package/src/core/errors.ts +49 -15
  194. package/src/core/gated.ts +22 -4
  195. package/src/core/http.ts +156 -46
  196. package/src/core/pagination.ts +38 -1
  197. package/src/generated/operations.ts +1 -1
  198. package/src/generated/types.ts +983 -173
  199. package/src/index.ts +64 -7
  200. package/src/resources/addresses.ts +23 -8
  201. package/src/resources/admin.ts +186 -33
  202. package/src/resources/approvals.ts +55 -9
  203. package/src/resources/balances.ts +186 -14
  204. package/src/resources/categories.ts +125 -30
  205. package/src/resources/counterparties.ts +23 -6
  206. package/src/resources/folders.ts +74 -18
  207. package/src/resources/history.ts +27 -2
  208. package/src/resources/me.ts +30 -5
  209. package/src/resources/members.ts +26 -7
  210. package/src/resources/operations.ts +73 -8
  211. package/src/resources/registries.ts +90 -11
  212. package/src/resources/reports.ts +348 -67
  213. package/src/resources/sharing.ts +71 -18
  214. package/src/resources/transfers.ts +33 -5
  215. package/src/resources/types.ts +20 -0
  216. package/src/units.ts +19 -2
  217. package/src/version.ts +1 -1
@@ -1,10 +1,29 @@
1
1
  import { seg, type HttpClient, type Query } from '../core/http.js'
2
2
  import type {
3
- ActivityReport,
3
+ AccountStatementReport,
4
+ AddressActivityReport,
5
+ AnyReport,
6
+ ApprovalActivityReport,
7
+ AssetMovementReport,
8
+ BalancePositionReport,
4
9
  CategoryAnalytics,
10
+ CounterpartyActivityReport,
11
+ CounterpartyGroupActivityReport,
12
+ CreateReportExportBody,
13
+ CreateReportSnapshotBody,
14
+ CreateReportTemplateBody,
15
+ DrilldownResponse,
16
+ OperationsExceptionsReport,
17
+ PatchReportTemplateBody,
18
+ ReportDrilldownBody,
5
19
  ReportExport,
20
+ ReportMeta,
6
21
  ReportSnapshot,
22
+ ReportSnapshotDetail as GeneratedReportSnapshotDetail,
23
+ ReportSnapshotList,
24
+ ReportSnapshotListItem as GeneratedReportSnapshotListItem,
7
25
  ReportTemplate,
26
+ TransactionActivityReport,
8
27
  } from '../generated/types.js'
9
28
 
10
29
  /**
@@ -12,18 +31,13 @@ import type {
12
31
  * the `?type=` alias, in a snapshot's `reportType` and in `report.type` of
13
32
  * the answer. Paths and the alias once used kebab-case while snapshots used
14
33
  * snake, so a client with a single `fetchReport` got a 400 on every snapshot.
34
+ *
35
+ * Taken from the document's own enum (`report.type` of an answer) rather than
36
+ * spelled out here, so a type the API grows shows up at once — as a compile
37
+ * error in `ReportParams` and `ReportResult`, which are keyed by it and would
38
+ * then lack an entry.
15
39
  */
16
- export type ReportType =
17
- | 'account_statement'
18
- | 'address_activity'
19
- | 'approval_activity'
20
- | 'asset_movement'
21
- | 'balance_position'
22
- | 'category_analytics'
23
- | 'counterparty_activity'
24
- | 'counterparty_group_activity'
25
- | 'operations_exceptions'
26
- | 'transaction_activity'
40
+ export type ReportType = ReportMeta['type']
27
41
 
28
42
  /**
29
43
  * Filters shared by the reporting endpoints.
@@ -41,8 +55,8 @@ export interface ReportFilters {
41
55
  to?: string | undefined
42
56
  timezone?: string | undefined
43
57
  /** Which date the period is measured by. */
44
- dateBasis?: string | undefined
45
- groupBy?: string | undefined
58
+ dateBasis?: ReportMeta['dateBasis'] | undefined
59
+ groupBy?: 'day' | 'week' | 'month' | undefined
46
60
  /** Currency the totals are expressed in. */
47
61
  baseCurrency?: string | undefined
48
62
  statuses?: readonly string[] | undefined
@@ -69,12 +83,12 @@ export interface ReportFilters {
69
83
  minimumAmount?: string | undefined
70
84
  maximumAmount?: string | undefined
71
85
  includeInternal?: boolean | undefined
72
- counterpartyClassification?: string | undefined
73
- folderClassification?: string | undefined
74
- comparison?: string | undefined
86
+ counterpartyClassification?: ReportMeta['classificationMode'] | undefined
87
+ folderClassification?: ReportMeta['classificationMode'] | undefined
88
+ comparison?: 'none' | 'previous_period' | 'previous_year' | undefined
75
89
  userId?: string | undefined
76
90
  /** Split the figures by owner of the address. */
77
- breakdownBy?: string | undefined
91
+ breakdownBy?: 'owner' | undefined
78
92
  /** Include addresses shared with you. */
79
93
  includeShared?: boolean | undefined
80
94
 
@@ -87,7 +101,7 @@ export interface ReportFilters {
87
101
  categoryKey?: string | undefined
88
102
  groupByField?: string | undefined
89
103
  metricField?: string | undefined
90
- agg?: string | undefined
104
+ agg?: 'sum' | 'avg' | 'min' | 'max' | 'median' | 'count' | undefined
91
105
  at?: string | undefined
92
106
  includeAdjustments?: boolean | undefined
93
107
  minimumAgeSeconds?: number | undefined
@@ -99,24 +113,28 @@ export interface ReportFilters {
99
113
 
100
114
  /**
101
115
  * What a snapshot looks like IN A LISTING — eleven fields, not the whole
102
- * record. The figures themselves (`result`) are not among them: a list of
103
- * snapshots would otherwise carry every report it mentions. Fetch one with
104
- * `snapshot(id)` when you need its contents.
116
+ * record. A strict alias of the generated schema.
117
+ *
118
+ * The figures themselves are not among them: a list of snapshots would
119
+ * otherwise carry every report it mentions. Fetch one with `snapshot(id)`
120
+ * when you need its contents.
105
121
  */
106
- export type ReportSnapshotListItem = Pick<
107
- ReportSnapshot,
108
- | 'id'
109
- | 'reportType'
110
- | 'schemaVersion'
111
- | 'parametersJson'
112
- | 'generatedById'
113
- | 'generatedAt'
114
- | 'dataCutoffAt'
115
- | 'rowCount'
116
- | 'resultHash'
117
- | 'status'
118
- | 'createdAt'
119
- >
122
+ export type ReportSnapshotListItem = GeneratedReportSnapshotListItem
123
+
124
+ /**
125
+ * A single snapshot, as `snapshot(id)` answers. A strict alias of the
126
+ * generated schema.
127
+ *
128
+ * The figures come back under `resultJson`, which is the column's own name.
129
+ * Only the CREATE call names the same thing `result` — hence two schemas,
130
+ * `ReportSnapshot` for `createSnapshot` and this one for the detail. Reading
131
+ * `result` off a detail, or `resultJson` off a fresh snapshot, is a compile
132
+ * error rather than an `undefined`.
133
+ *
134
+ * `resultJson` is `null` only for a snapshot that did not finish
135
+ * (`status` other than `completed`).
136
+ */
137
+ export type ReportSnapshotDetail = GeneratedReportSnapshotDetail
120
138
 
121
139
  /** A period. Required by every report except `balance_position`. */
122
140
  interface Period {
@@ -133,10 +151,29 @@ interface Period {
133
151
  */
134
152
  export interface ReportParams {
135
153
  account_statement: Period
136
- address_activity: Period
154
+ /**
155
+ * Exactly one of the two. Neither, or both, is a 400 — and from this
156
+ * endpoint it arrives as `REPORT_INVALID_PERIOD`, a code that points at the
157
+ * dates and sends the reader looking in the wrong place.
158
+ */
159
+ address_activity: Period &
160
+ ({ addressId: string; folderId?: never } | { folderId: string; addressId?: never })
137
161
  approval_activity: Period
138
162
  asset_movement: Period & { tokenId: string }
139
- balance_position: Record<string, never>
163
+ /**
164
+ * Nothing is required: the moment is `?at=` (omitted means now), and it
165
+ * lives in `ReportFilters` with the rest of what this report reads —
166
+ * `tokenIds`, `addressIds`, `folderIds`, `userId`, `includeShared`.
167
+ *
168
+ * `Record<never, never>` and NOT `Record<string, never>`. The latter is an
169
+ * easy thing to reach for and silently breaks the type: it carries an index
170
+ * signature, and in the intersection `ReportFilters & ReportParams[T]` that
171
+ * signature applies to every filter as well, narrowing each of them to
172
+ * `never`. The report then accepted no arguments at all — `{}` compiled and
173
+ * `{ at }` did not. `Record<never, never>` has no index signature, so it
174
+ * contributes no keys and takes none away.
175
+ */
176
+ balance_position: Record<never, never>
140
177
  category_analytics: Period & { categoryKey: string }
141
178
  counterparty_activity: Period & { counterpartyId: string }
142
179
  counterparty_group_activity: Period & { counterpartyGroupId: string }
@@ -144,18 +181,130 @@ export interface ReportParams {
144
181
  transaction_activity: Period
145
182
  }
146
183
 
147
- /** `category_analytics` answers with its own shape, not the activity report. */
184
+ /**
185
+ * The report filters a DRILL-DOWN honours.
186
+ *
187
+ * Deliberately narrower than `ReportFilters`: the drill-down endpoint takes
188
+ * `filters` as a free-form bag and reads only these keys out of it, so
189
+ * anything else is accepted and then dropped — the silence that makes a
190
+ * figure and its breakdown disagree. Built with `Pick` so the two cannot
191
+ * drift apart as filters are added.
192
+ */
193
+ export type DrilldownFilters = Pick<
194
+ ReportFilters,
195
+ | 'from'
196
+ | 'to'
197
+ | 'dateBasis'
198
+ | 'tokenIds'
199
+ | 'networkIds'
200
+ | 'addressIds'
201
+ | 'ownAddressIds'
202
+ | 'folderIds'
203
+ | 'counterpartyIds'
204
+ | 'counterpartyGroupIds'
205
+ | 'counterpartyAddressIds'
206
+ | 'statuses'
207
+ | 'flowTypes'
208
+ | 'categories'
209
+ | 'categoryKeys'
210
+ | 'categoryField'
211
+ | 'initiatorIds'
212
+ | 'decisionPolicyIds'
213
+ | 'counterpartyClassification'
214
+ | 'userId'
215
+ > & {
216
+ /** `in`/`out`. The query-string reports have no equivalent filter. */
217
+ directions?: readonly ('in' | 'out')[] | undefined
218
+ /**
219
+ * A STRING, not the boolean this flag is everywhere else in the package.
220
+ * `filters` reaches the handler unparsed and the comparison there is
221
+ * against `'true'`, so a boolean `true` is read as "not shared" and the
222
+ * shared addresses quietly drop out of the list.
223
+ */
224
+ includeShared?: 'true' | 'false' | undefined
225
+ }
226
+
227
+ /**
228
+ * WHICH cell of the report was clicked.
229
+ *
230
+ * These REFINE `filters`; where a key means the same thing in both, the click
231
+ * REPLACES the filter — with one exception, `categoryField`, where the two
232
+ * are combined instead. `periodStart` alone covers the following 24 hours;
233
+ * pass `periodEnd` for any other span.
234
+ */
235
+ export interface DrilldownSelection {
236
+ direction?: 'in' | 'out' | undefined
237
+ periodStart?: string | undefined
238
+ periodEnd?: string | undefined
239
+ tokenId?: string | undefined
240
+ ownAddressId?: string | undefined
241
+ counterpartyId?: string | undefined
242
+ counterpartyAddressId?: string | undefined
243
+ status?: string | undefined
244
+ category?: string | undefined
245
+ /**
246
+ * One group of a category-field report: `ticker:EUR`. COMBINED with
247
+ * `filters.categoryField` rather than replacing it, so a report already
248
+ * narrowed by field stays narrowed when a group of it is opened.
249
+ */
250
+ categoryField?: string | undefined
251
+ initiatorId?: string | undefined
252
+ decisionPolicyId?: string | undefined
253
+ }
254
+
255
+ /** `ReportDrilldownBody` minus the two free-form bags refined below. */
256
+ type DrilldownEnvelope = Omit<ReportDrilldownBody, 'filters' | 'drilldown'>
257
+
258
+ /**
259
+ * Body of the drill-down request.
260
+ *
261
+ * The generated `ReportDrilldownBody` is the same request with `filters` and
262
+ * `drilldown` as free-form bags (`{ [key: string]: unknown }`) — which is
263
+ * what the API validates, and also why it is not used as-is: the endpoint
264
+ * reads a fixed set of keys out of each bag and drops the rest in silence.
265
+ * `DrilldownFilters` and `DrilldownSelection` name those keys. Everything
266
+ * else is taken from the generated schema, so a field the API adds to the
267
+ * envelope appears here without an edit:
268
+ *
269
+ * • `reportType` — informational, the handler does not branch on it;
270
+ * • `snapshotId` — build the list from a snapshot's manifest instead of
271
+ * from live data;
272
+ * • `limit` — 1..500, 100 by default;
273
+ * • `cursor` — `nextCursor` of the previous page.
274
+ *
275
+ * Each of those may also be passed as an explicit `undefined`, which is left
276
+ * out of the request.
277
+ */
278
+ export type DrilldownQuery = {
279
+ [K in keyof DrilldownEnvelope]: DrilldownEnvelope[K] | undefined
280
+ } & {
281
+ filters?: DrilldownFilters | undefined
282
+ drilldown?: DrilldownSelection | undefined
283
+ }
284
+
285
+ /**
286
+ * What each report type ANSWERS with — one generated schema per type.
287
+ *
288
+ * The ten reports share almost nothing beyond `report` (and, except for
289
+ * `category_analytics`, `dataQuality`): a position report has `totalsByAsset`
290
+ * and no `summary`, an approval report counts requests rather than money, an
291
+ * account statement is opening and closing balances. So `run('x', …)` hands
292
+ * back the schema of `x`, and a block that report does not have is a compile
293
+ * error instead of an `undefined` at runtime.
294
+ *
295
+ * `ReportResult[ReportType]` — every entry at once — is `AnyReport`.
296
+ */
148
297
  export interface ReportResult {
149
- account_statement: ActivityReport
150
- address_activity: ActivityReport
151
- approval_activity: ActivityReport
152
- asset_movement: ActivityReport
153
- balance_position: ActivityReport
298
+ account_statement: AccountStatementReport
299
+ address_activity: AddressActivityReport
300
+ approval_activity: ApprovalActivityReport
301
+ asset_movement: AssetMovementReport
302
+ balance_position: BalancePositionReport
154
303
  category_analytics: CategoryAnalytics
155
- counterparty_activity: ActivityReport
156
- counterparty_group_activity: ActivityReport
157
- operations_exceptions: ActivityReport
158
- transaction_activity: ActivityReport
304
+ counterparty_activity: CounterpartyActivityReport
305
+ counterparty_group_activity: CounterpartyGroupActivityReport
306
+ operations_exceptions: OperationsExceptionsReport
307
+ transaction_activity: TransactionActivityReport
159
308
  }
160
309
 
161
310
  /**
@@ -184,7 +333,22 @@ export class ReportsResource {
184
333
  return `/api/v1/vaults/${this.#vault}`
185
334
  }
186
335
 
187
- /** Runs a report. The compiler requires whatever that type needs. */
336
+ /**
337
+ * Runs a report. The compiler requires whatever that type needs, and the
338
+ * answer is the schema of THAT type — see `ReportResult`.
339
+ *
340
+ * Money in a report is DECIMAL, not raw smallest units: every `AssetAmount`
341
+ * (`…ByAsset[].amount`), the `byAsset` rows, and the balances of
342
+ * `asset_movement`, `account_statement` and `balance_position` are strings
343
+ * like `"1.5"`. Read them with `parseDecimal`; `amount` would take `"1.5"`
344
+ * for an integer and reject it. The one exception is `address_activity`,
345
+ * whose `openingBalances`/`closingBalances` are raw (`balanceRaw`).
346
+ *
347
+ * Percentages — `comparison.*ChangePercent`, `observations[].changePercent`,
348
+ * the `…ShareUsd` and `…RatePercent` fields — are decimal STRINGS too
349
+ * (`"12.5"`, `"-40"`), and `null` where the figure cannot be worked out:
350
+ * no previous period to compare with, no USD valuation, a zero base.
351
+ */
188
352
  run<T extends ReportType>(
189
353
  type: T,
190
354
  params: ReportFilters & ReportParams[T],
@@ -194,7 +358,14 @@ export class ReportsResource {
194
358
  })
195
359
  }
196
360
 
197
- /** The same thing through the unified endpoint, choosing the type by alias. */
361
+ /**
362
+ * The same thing through the unified endpoint, choosing the type by alias.
363
+ *
364
+ * The document types this endpoint's answer as `AnyReport`, the union of
365
+ * all ten. The type you pass decides which member arrives, so the return is
366
+ * narrowed to it; call it with a `ReportType` known only at runtime and you
367
+ * get the whole union back.
368
+ */
198
369
  runUnified<T extends ReportType>(
199
370
  type: T,
200
371
  params: ReportFilters & ReportParams[T],
@@ -204,9 +375,30 @@ export class ReportsResource {
204
375
  })
205
376
  }
206
377
 
207
- /** Drill-down: the operations behind a figure in a report. */
208
- queryOperations(body: unknown): Promise<unknown> {
209
- return this.#http.request('POST', `${this.#base}/reports/query/operations`, { body })
378
+ /**
379
+ * Drill-down: the operations behind a figure in a report.
380
+ *
381
+ * A PERIOD IS REQUIRED even though every field of the body is optional. The
382
+ * endpoint takes it from `drilldown.periodStart` or else from
383
+ * `filters.from`/`filters.to`, and with neither it answers 400
384
+ * `REPORT_INVALID_PERIOD` — `queryOperations({})` is not a "whole history"
385
+ * query.
386
+ *
387
+ * The other defaults narrow harder than people expect: with no
388
+ * `filters.statuses` only `confirmed` operations are listed, and with no
389
+ * `filters.flowTypes` only `external` ones. Pass the report's own filters
390
+ * through, or the breakdown comes back smaller than the figure it explains.
391
+ *
392
+ * `totals.byAsset[].amount` is a DECIMAL string, like every `AssetAmount`
393
+ * in the reports. Read it with `parseDecimal`, not `amount` — the latter
394
+ * would take `'1.5'` for an integer and reject it.
395
+ */
396
+ queryOperations(body: DrilldownQuery = {}): Promise<DrilldownResponse> {
397
+ return this.#http.request<DrilldownResponse>(
398
+ 'POST',
399
+ `${this.#base}/reports/query/operations`,
400
+ { body },
401
+ )
210
402
  }
211
403
 
212
404
  // --- saved templates ----------------------------------------------------
@@ -223,11 +415,49 @@ export class ReportsResource {
223
415
  )
224
416
  }
225
417
 
226
- createTemplate(body: unknown): Promise<ReportTemplate> {
418
+ /**
419
+ * Saves a set of report parameters under a name.
420
+ *
421
+ * `parameters` is what the live report takes, as an object — with one
422
+ * addition: instead of `from`/`to` it may carry
423
+ * `relativePeriod: { unit: 'day' | 'week' | 'month', offset }`, resolved to
424
+ * dates each time the template runs.
425
+ *
426
+ * NOTHING IN `parameters` IS CHECKED ON SAVE — it is a free-form bag to the
427
+ * API. A template with no period, or one aimed at a counterparty that does
428
+ * not exist, saves fine and fails at `runTemplate`.
429
+ *
430
+ * `isShared: true` makes the template visible to the whole vault and is for
431
+ * administrators only. Sharing lends nobody the author's reach: the scope
432
+ * is worked out for whoever RUNS it, on every run, so two people get
433
+ * different figures from the same template.
434
+ *
435
+ * THE BAG IS NOT A QUERY STRING, and three things that work on a live
436
+ * report silently do not work here. It is stored and later handed to the
437
+ * report builder as it is — nothing parses it, nothing fills defaults:
438
+ *
439
+ * - a multi-value filter must be a real ARRAY; a comma-separated string is
440
+ * not split;
441
+ * - `categoryField` IS IGNORED. A live report parses `'ticker:EUR'` into a
442
+ * filter; this path does not, so a template or snapshot built from the
443
+ * filters of a report narrowed by `categoryField` answers 200 with
444
+ * figures for EVERY value — the wrong report, with no error. Until the
445
+ * API parses it here, send the parsed form the builder reads instead:
446
+ * `categoryFieldValues: { ticker: ['EUR'] }`;
447
+ * - `timezone` has no default. The live endpoints fall back to `UTC`; here
448
+ * an absent one reaches the builder as `undefined`. Always pass it.
449
+ *
450
+ * So spreading a `ReportFilters` value into `parameters` is safe only when
451
+ * it carries no `categoryField` and does carry a `timezone`. (The spread
452
+ * itself is needed because the type is an index signature, which an
453
+ * interface-typed value does not fit directly.)
454
+ */
455
+ createTemplate(body: CreateReportTemplateBody): Promise<ReportTemplate> {
227
456
  return this.#http.request<ReportTemplate>('POST', `${this.#base}/report-templates`, { body })
228
457
  }
229
458
 
230
- updateTemplate(templateId: string, body: unknown): Promise<ReportTemplate> {
459
+ /** Partial: only the fields you send change. `parameters` is replaced whole, not merged. */
460
+ updateTemplate(templateId: string, body: PatchReportTemplateBody): Promise<ReportTemplate> {
231
461
  return this.#http.request<ReportTemplate>(
232
462
  'PATCH',
233
463
  `${this.#base}/report-templates/${seg(templateId)}`,
@@ -235,7 +465,7 @@ export class ReportsResource {
235
465
  )
236
466
  }
237
467
 
238
- deleteTemplate(templateId: string): Promise<unknown> {
468
+ deleteTemplate(templateId: string): Promise<{ deleted: true }> {
239
469
  return this.#http.request('DELETE', `${this.#base}/report-templates/${seg(templateId)}`)
240
470
  }
241
471
 
@@ -247,9 +477,21 @@ export class ReportsResource {
247
477
  * template's period while believing you had asked for another one. To
248
478
  * change the filters, read the template and call `run()` with them, or
249
479
  * edit the template.
480
+ *
481
+ * The answer is `AnyReport`: which of the ten shapes arrives depends on the
482
+ * template's `reportType`, which is not known to the compiler. `report.type`
483
+ * says which one it is at runtime, but it does NOT narrow the union — the
484
+ * document types that field as the same ten-value enum on every member. So
485
+ * narrow by a block only that report has (`'totalsByAsset' in report`), or
486
+ * check `report.type` and assert the type yourself.
487
+ *
488
+ * This is also where a template's mistakes surface, since its parameters
489
+ * were not checked when it was saved: no period is a 400, a target that no
490
+ * longer exists a 404, and a template built for somebody else's `userId`
491
+ * run by a non-administrator a 403.
250
492
  */
251
- runTemplate(templateId: string): Promise<ActivityReport> {
252
- return this.#http.request<ActivityReport>(
493
+ runTemplate(templateId: string): Promise<AnyReport> {
494
+ return this.#http.request<AnyReport>(
253
495
  'POST',
254
496
  `${this.#base}/report-templates/${seg(templateId)}/run`,
255
497
  )
@@ -267,27 +509,64 @@ export class ReportsResource {
267
509
  */
268
510
  snapshots(
269
511
  params: { reportType?: ReportType; limit?: number } = {},
270
- ): Promise<{ items: ReportSnapshotListItem[] }> {
271
- return this.#http.request('GET', `${this.#base}/report-snapshots`, { query: { ...params } })
512
+ ): Promise<ReportSnapshotList> {
513
+ return this.#http.request<ReportSnapshotList>('GET', `${this.#base}/report-snapshots`, {
514
+ query: { ...params },
515
+ })
272
516
  }
273
517
 
274
- snapshot(snapshotId: string): Promise<ReportSnapshot> {
275
- return this.#http.request<ReportSnapshot>(
518
+ /**
519
+ * One snapshot WITH its figures — under `resultJson`, not `result`; see
520
+ * `ReportSnapshotDetail`. A non-admin can only read snapshots they
521
+ * generated themselves, and a missing one is 404 either way, so "not yours"
522
+ * and "not there" are indistinguishable.
523
+ */
524
+ snapshot(snapshotId: string): Promise<ReportSnapshotDetail> {
525
+ return this.#http.request<ReportSnapshotDetail>(
276
526
  'GET',
277
527
  `${this.#base}/report-snapshots/${seg(snapshotId)}`,
278
528
  )
279
529
  }
280
530
 
281
- createSnapshot(body: unknown): Promise<ReportSnapshot> {
531
+ /**
532
+ * Builds the report and freezes it. The answer carries the figures under
533
+ * `result` — the detail (`snapshot(id)`) calls the same thing `resultJson`.
534
+ *
535
+ * `parameters` follows the same rules as a template's — read them at
536
+ * `createTemplate`, because two of them fail silently: `categoryField` is
537
+ * ignored (the snapshot would freeze UNFILTERED figures; send
538
+ * `categoryFieldValues` instead) and `timezone` has no default. Unlike a
539
+ * template the parameters ARE used at once, so a bad period or a missing
540
+ * target fails here.
541
+ *
542
+ * `result` is the report of type `reportType`, but the type cannot say so:
543
+ * the spec declares it as a free-form object. Narrow it yourself —
544
+ * `snap.result as unknown as ReportResult['transaction_activity']` (the
545
+ * double cast is required; the detail's `resultJson` takes the same one).
546
+ * For the same reason the per-type REQUIRED parameters are not checked by
547
+ * the compiler here, as they are in `run()`.
548
+ *
549
+ * A result over roughly 900 KB is refused with 400
550
+ * `REPORT_PERIOD_TOO_LARGE` — narrow the filters.
551
+ */
552
+ createSnapshot(body: CreateReportSnapshotBody): Promise<ReportSnapshot> {
282
553
  return this.#http.request<ReportSnapshot>('POST', `${this.#base}/report-snapshots`, { body })
283
554
  }
284
555
 
285
- deleteSnapshot(snapshotId: string): Promise<unknown> {
556
+ deleteSnapshot(snapshotId: string): Promise<{ deleted: true }> {
286
557
  return this.#http.request('DELETE', `${this.#base}/report-snapshots/${seg(snapshotId)}`)
287
558
  }
288
559
 
289
- /** Starts an export of a snapshot. The file is fetched separately. */
290
- createExport(snapshotId: string, body: unknown): Promise<ReportExport> {
560
+ /**
561
+ * Exports a snapshot. The file is fetched separately, with `downloadExport`.
562
+ *
563
+ * Only `format` matters, and only two of its four values work: `json` (the
564
+ * frozen result) and `csv` (a flat register of operations). `xlsx` and
565
+ * `pdf` pass validation and are then refused with 400
566
+ * `REPORT_UNSUPPORTED_FORMAT`. `locale`, `timezone` and
567
+ * `includeOperationRegister` are accepted and currently change nothing.
568
+ */
569
+ createExport(snapshotId: string, body: CreateReportExportBody): Promise<ReportExport> {
291
570
  return this.#http.request<ReportExport>(
292
571
  'POST',
293
572
  `${this.#base}/report-snapshots/${seg(snapshotId)}/exports`,
@@ -307,7 +586,9 @@ export class ReportsResource {
307
586
  * be CSV or another binary format, and parsing that as JSON fails every
308
587
  * time. Read it with `.blob()`, `.text()` or `.arrayBuffer()`.
309
588
  *
310
- * Poll `export()` until it reports ready before calling this.
589
+ * Exports are produced synchronously today, so the record `createExport`
590
+ * answers with is already `completed` and can be downloaded at once. The
591
+ * `status` field exists for a queued path — check it rather than assuming.
311
592
  */
312
593
  downloadExport(exportId: string): Promise<Response> {
313
594
  return this.#http.fetchRaw('GET', `${this.#base}/report-exports/${seg(exportId)}/download`)
@@ -6,7 +6,7 @@ import type {
6
6
  GrantAddressShareBody,
7
7
  ParticipantsResponse,
8
8
  PatchAddressShareBody,
9
- ShareBatchResult,
9
+ ShareGrantCreated,
10
10
  } from '../generated/types.js'
11
11
 
12
12
  /**
@@ -52,14 +52,40 @@ export class SharingResource {
52
52
  * 400 comes only when every subject was rejected. A consumer that trusts
53
53
  * the status code silently loses the people who were refused.
54
54
  *
55
+ * WHERE THE REASONS GO WHEN EVERY SUBJECT FAILED. That 400 is not an error
56
+ * envelope: the API sends the same `{results:[…]}` body with a 400 instead
57
+ * of a 201. The document describes both forms of that status — the
58
+ * envelope for a body that failed validation, and `ShareBatchAllRejected`
59
+ * for a batch in which every subject was refused. The SDK does not lose the
60
+ * second, but it does throw: `core/errors.ts` finds neither `error` nor
61
+ * `code` in the body, so it builds a `CoreError` with `code: 'BAD_REQUEST'`
62
+ * and puts the WHOLE body in `details`:
63
+ *
64
+ * ```ts
65
+ * catch (e) {
66
+ * if (isCoreError(e) && e.status === 400) {
67
+ * const per = (e.details as ShareBatchAllRejected | undefined)?.results
68
+ * // per[i].error.code — why that one subject was refused
69
+ * }
70
+ * }
71
+ * ```
72
+ *
73
+ * `details` is `undefined` for the other 400, the validation one — hence
74
+ * the `| undefined` in the cast, and the reason to check before reading.
75
+ *
76
+ * Reading `message` instead gets you "The request was rejected", because
77
+ * the server sent no message to pass on. Per-subject `error.message` equals
78
+ * its `code`, so neither is a sentence to show a person as-is.
79
+ *
55
80
  * The single-subject form answers with the grant itself rather than a
56
- * batch, which is why the return type is a union.
81
+ * batch, which is why the return type is a union — `ShareGrantCreated`,
82
+ * the grant or the batch; tell them apart with `'results' in answer`. It
83
+ * also reports failure differently: one subject refused is a plain 403 with
84
+ * no `results[]` at all, so the batch form is the one to use when you need
85
+ * reasons.
57
86
  */
58
- share(
59
- addressId: string,
60
- input: GrantAddressShareBody,
61
- ): Promise<ShareBatchResult | AddressAccessGrant> {
62
- return this.#http.request<ShareBatchResult | AddressAccessGrant>(
87
+ share(addressId: string, input: GrantAddressShareBody): Promise<ShareGrantCreated> {
88
+ return this.#http.request<ShareGrantCreated>(
63
89
  'POST',
64
90
  `${this.#address(addressId)}/shares`,
65
91
  { body: input },
@@ -78,8 +104,12 @@ export class SharingResource {
78
104
  )
79
105
  }
80
106
 
81
- revoke(addressId: string, shareId: string): Promise<unknown> {
82
- return this.#http.request('DELETE', `${this.#address(addressId)}/shares/${seg(shareId)}`)
107
+ /** Soft: the row survives with `revokedAt`/`revokedById` for the audit trail. */
108
+ revoke(addressId: string, shareId: string): Promise<{ revoked: boolean }> {
109
+ return this.#http.request<{ revoked: boolean }>(
110
+ 'DELETE',
111
+ `${this.#address(addressId)}/shares/${seg(shareId)}`,
112
+ )
83
113
  }
84
114
 
85
115
  /**
@@ -94,19 +124,42 @@ export class SharingResource {
94
124
  )
95
125
  }
96
126
 
97
- /** Addresses you have shared with others. */
98
- sharedByMe(): Promise<unknown> {
99
- return this.#http.request('GET', `/api/v1/vaults/${this.#vault}/shared-by-me`)
127
+ /**
128
+ * What you have opened to others — addresses AND folders, so the entries
129
+ * are not all of one `targetType`. Same shape as `list()`.
130
+ */
131
+ sharedByMe(): Promise<AddressSharesList> {
132
+ return this.#http.request<AddressSharesList>(
133
+ 'GET',
134
+ `/api/v1/vaults/${this.#vault}/shared-by-me`,
135
+ )
100
136
  }
101
137
 
102
- /** Addresses others have shared with you. */
103
- sharedWithMe(): Promise<unknown> {
104
- return this.#http.request('GET', `/api/v1/vaults/${this.#vault}/shared-with-me`)
138
+ /**
139
+ * Addresses others have shared with you — DIRECT grants on an address
140
+ * only. Access that reaches you through a shared FOLDER is not here, which
141
+ * is the trap: an empty answer does not mean nothing is shared with you.
142
+ * For shared access of every kind ask the addresses list with
143
+ * `access: 'shared'`, or `'all'` to include your own.
144
+ */
145
+ sharedWithMe(): Promise<AddressSharesList> {
146
+ return this.#http.request<AddressSharesList>(
147
+ 'GET',
148
+ `/api/v1/vaults/${this.#vault}/shared-with-me`,
149
+ )
105
150
  }
106
151
 
107
- /** Gives up access someone granted you. Your own grant, your own decision. */
108
- leave(grantId: string): Promise<unknown> {
109
- return this.#http.request(
152
+ /**
153
+ * Gives up access someone granted you. Your own grant, your own decision —
154
+ * the owner is not asked.
155
+ *
156
+ * Works on a grant made to you PERSONALLY. Access you have through a user
157
+ * group cannot be dropped this way, because it belongs to the whole group:
158
+ * that answers 409 `ACCESS_VIA_GROUP`, with the group's id and name in
159
+ * `details`, so the interface can say which group to leave instead.
160
+ */
161
+ leave(grantId: string): Promise<{ left: boolean }> {
162
+ return this.#http.request<{ left: boolean }>(
110
163
  'DELETE',
111
164
  `/api/v1/vaults/${this.#vault}/shared-with-me/${seg(grantId)}`,
112
165
  )