@v3finance/core-sdk 0.1.0 → 0.2.1

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 +441 -7
  2. package/README.md +219 -73
  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 +30 -6
  12. package/dist/cjs/core/gated.d.ts.map +1 -1
  13. package/dist/cjs/core/gated.js +21 -6
  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 +147 -43
  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 +965 -179
  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 +23 -8
  31. package/dist/cjs/resources/addresses.d.ts.map +1 -1
  32. package/dist/cjs/resources/addresses.js +8 -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 +44 -6
  86. package/dist/cjs/resources/transfers.d.ts.map +1 -1
  87. package/dist/cjs/resources/transfers.js +41 -3
  88. package/dist/cjs/resources/transfers.js.map +1 -1
  89. package/dist/cjs/resources/types.d.ts +16 -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 +30 -6
  105. package/dist/esm/core/gated.d.ts.map +1 -1
  106. package/dist/esm/core/gated.js +21 -6
  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 +148 -44
  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 +965 -179
  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 +23 -8
  124. package/dist/esm/resources/addresses.d.ts.map +1 -1
  125. package/dist/esm/resources/addresses.js +8 -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 +44 -6
  179. package/dist/esm/resources/transfers.d.ts.map +1 -1
  180. package/dist/esm/resources/transfers.js +41 -3
  181. package/dist/esm/resources/transfers.js.map +1 -1
  182. package/dist/esm/resources/types.d.ts +16 -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 +193 -0
  190. package/docs/errors.md +491 -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 +31 -7
  195. package/src/core/http.ts +173 -49
  196. package/src/core/pagination.ts +38 -1
  197. package/src/generated/operations.ts +1 -1
  198. package/src/generated/types.ts +1001 -180
  199. package/src/index.ts +64 -7
  200. package/src/resources/addresses.ts +24 -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 +51 -7
  215. package/src/resources/types.ts +16 -0
  216. package/src/units.ts +19 -2
  217. package/src/version.ts +1 -1
@@ -1,18 +1,142 @@
1
1
  import { seg, type HttpClient } from '../core/http.js'
2
2
  import type { PageParams } from '../core/pagination.js'
3
- import type { BalancesAsOf, LedgerEntry, VaultBalancesResponse } from '../generated/types.js'
3
+ import type {
4
+ BalancesAsOf,
5
+ BalancesAsOfAccount,
6
+ LedgerAccountItem,
7
+ LedgerEntriesPage,
8
+ LedgerEntry,
9
+ LedgerPosting as GeneratedLedgerPosting,
10
+ VaultBalancesResponse,
11
+ } from '../generated/types.js'
12
+
13
+ /**
14
+ * What a ledger account is FOR. A strict alias of the generated enum.
15
+ *
16
+ * `available` (spendable) and `reserved` (held for a transfer in flight)
17
+ * belong to an ADDRESS. `external` and `adjustment` are vault-level: they are
18
+ * the far side of the double entry — the outside world, and manual
19
+ * corrections — and belong to no address, which is why
20
+ * `LedgerAccountItem.address` is nullable.
21
+ */
22
+ export type LedgerPurpose = LedgerAccountItem['purpose']
4
23
 
5
24
  export interface BalancesParams extends PageParams {
6
25
  /** Roll up the whole vault instead of listing per address. */
7
26
  summary?: boolean | undefined
8
27
  addressId?: string | undefined
9
28
  token?: string | undefined
10
- /** `available` is spendable; `reserved` is held for transfers in flight. */
11
- purpose?: 'available' | 'reserved' | undefined
29
+ /**
30
+ * Narrows `accounts` ONLY. `totals` ignores it and always reports
31
+ * `available` plus `reserved`, so `purpose: 'external'` lists the
32
+ * vault-level accounts beside totals that do not count them — the two
33
+ * halves of the answer are not filtered the same way.
34
+ */
35
+ purpose?: LedgerPurpose | undefined
12
36
  /** Include addresses shared with you. Off by default. */
13
37
  includeShared?: boolean | undefined
14
38
  }
15
39
 
40
+ /**
41
+ * `balances/as-of` parameters, beyond the moment itself.
42
+ *
43
+ * A superset of what `list()` takes: a replay can also be narrowed to a token
44
+ * id or a folder.
45
+ *
46
+ * Written by hand, like every query type in this package: the generator emits
47
+ * schemas and never looks at `parameters`, so there is no generated shape to
48
+ * alias. `test/queryParams.test.ts` holds it to the document instead — a
49
+ * parameter the endpoint declares and this interface lacks fails there.
50
+ */
51
+ export interface BalancesAsOfParams {
52
+ /** Token code, e.g. `usdt`. Ignored when `tokenId` is given. */
53
+ token?: string | undefined
54
+ /** Registry token id. Wins over `token`. */
55
+ tokenId?: string | undefined
56
+ addressId?: string | undefined
57
+ /**
58
+ * Every address in the folder, by its CURRENT membership — a replay of past
59
+ * balances through today's folder. REPLACES `addressId` rather than
60
+ * intersecting with it, so sending both silently drops `addressId`.
61
+ */
62
+ folderId?: string | undefined
63
+ purpose?: LedgerPurpose | undefined
64
+ /** Totals only. See the overloads: `accounts` is then ABSENT, not empty. */
65
+ summary?: boolean | undefined
66
+ includeShared?: boolean | undefined
67
+ }
68
+
69
+ /**
70
+ * What `asOf` answers WITHOUT `summary` — the body with `accounts` present.
71
+ *
72
+ * The generated `BalancesAsOf` describes both modes with one schema, so its
73
+ * `accounts` is optional: absent under `summary: true`, always there
74
+ * otherwise (an empty array when nothing moved before the moment). This is
75
+ * the second half of that sentence as a type, so a call that did not ask for
76
+ * a summary is not made to check for a key that cannot be missing.
77
+ */
78
+ export type BalancesAsOfWithAccounts = BalancesAsOf & { accounts: BalancesAsOfAccount[] }
79
+
80
+ /**
81
+ * What `asOf` answers under `summary: true` — the same body WITHOUT
82
+ * `accounts`.
83
+ *
84
+ * Not an empty array: the key is absent, which is how the API tells "accounts
85
+ * were not asked for" from "there are none". The document says the same —
86
+ * `BalancesAsOf.accounts` is optional — and this type is the summary half of
87
+ * it, with the key removed rather than left optional.
88
+ *
89
+ * Spellable without importing it: `Omit<BalancesAsOf, 'accounts'>`.
90
+ */
91
+ export type BalancesAsOfSummary = Omit<BalancesAsOf, 'accounts'>
92
+
93
+ /**
94
+ * One side of a ledger entry. A strict alias of the generated schema.
95
+ *
96
+ * Signed, with no `direction` field: which way the money went IS the sign of
97
+ * `amountRawSigned` — negative means debited from that account. A COMPLETE
98
+ * entry's postings sum to zero; see `entries` for why the ones you are shown
99
+ * may not. `ownerId` is the address id, or the vault id for a vault-level
100
+ * account, and `amountDecimalSigned` falls back to the raw figure when the
101
+ * posting's token is not in the registry.
102
+ */
103
+ export type LedgerPosting = GeneratedLedgerPosting
104
+
105
+ /**
106
+ * The values `entryType` takes, as the journal REPORTS them.
107
+ *
108
+ * Stored as `reserve`/`confirm`/`release` and renamed on the way out, which
109
+ * is why the filter accepts either spelling: `entryType: 'reserve'` and
110
+ * `entryType: 'transfer_reserve'` select the same rows. `topup`, `adjustment`
111
+ * and `genesis` have no transfer stage and are not renamed. `genesis` is the
112
+ * opening correction written when the ledger was first backfilled; nothing
113
+ * creates new ones.
114
+ *
115
+ * Kept by hand because the document does not carry it as a type: the
116
+ * generated `LedgerEntry.entryType` is a bare `string` whose DESCRIPTION
117
+ * lists these six. The API does not close the list either — a stored kind it
118
+ * has no name for is passed through as it is — so this union types the
119
+ * FILTER, where a misspelling answers an empty page rather than an error,
120
+ * and is not forced onto the entries that come back.
121
+ */
122
+ export type LedgerEntryType =
123
+ | 'transfer_reserve'
124
+ | 'transfer_confirm'
125
+ | 'transfer_refund'
126
+ | 'topup'
127
+ | 'adjustment'
128
+ | 'genesis'
129
+
130
+ /**
131
+ * One ledger entry. A strict alias of the generated `LedgerEntry`, kept under
132
+ * its earlier name.
133
+ *
134
+ * `token` is the entry's token by its FIRST posting, taken before postings
135
+ * are filtered for the caller — so it is present even when `postings` comes
136
+ * back empty for you.
137
+ */
138
+ export type LedgerEntryItem = LedgerEntry
139
+
16
140
  /**
17
141
  * Balances and the ledger behind them.
18
142
  *
@@ -38,31 +162,79 @@ export class BalancesResource {
38
162
  )
39
163
  }
40
164
 
41
- /** Balances as they stood at a moment in time, replayed from the ledger. */
42
- asOf(at: string, params: { addressId?: string; token?: string } = {}): Promise<BalancesAsOf> {
43
- return this.#http.request<BalancesAsOf>(
165
+ /**
166
+ * Balances as they stood at a moment in time, replayed from the ledger.
167
+ *
168
+ * `at` is positional because it is required — a replay with no instant is
169
+ * not a replay. `totals` covers `available` and `reserved` only; the
170
+ * vault-level accounts appear in `accounts` and never in the totals.
171
+ *
172
+ * UNITS DIFFER FROM `list()`. In `totals`, `available` and `reserved` are
173
+ * DECIMAL here — duplicates of `availableDecimal`/`reservedDecimal` — while
174
+ * the fields of the same name on `list()` are raw smallest units. Raw
175
+ * figures exist only per account, in `accounts[].balance`. Read the totals
176
+ * with `parseDecimal`, not `amount`.
177
+ */
178
+ asOf(
179
+ at: string,
180
+ params?: BalancesAsOfParams & { summary?: false | undefined },
181
+ ): Promise<BalancesAsOfWithAccounts>
182
+ /** `summary: true` — totals only, and the body carries no `accounts` key. */
183
+ asOf(
184
+ at: string,
185
+ params: BalancesAsOfParams & { summary: true },
186
+ ): Promise<BalancesAsOfSummary>
187
+ /**
188
+ * `summary` decided at runtime. The answer is then one shape or the other,
189
+ * so this signature hands back the union rather than guessing: narrow it
190
+ * with `'accounts' in result`.
191
+ */
192
+ asOf(
193
+ at: string,
194
+ params: BalancesAsOfParams,
195
+ ): Promise<BalancesAsOfWithAccounts | BalancesAsOfSummary>
196
+ asOf(
197
+ at: string,
198
+ params: BalancesAsOfParams = {},
199
+ ): Promise<BalancesAsOfWithAccounts | BalancesAsOfSummary> {
200
+ return this.#http.request<BalancesAsOfWithAccounts | BalancesAsOfSummary>(
44
201
  'GET',
45
202
  `/api/v1/vaults/${this.#vault}/balances/as-of`,
46
203
  { query: { at, ...params } },
47
204
  )
48
205
  }
49
206
 
50
- /** The raw ledger. Append-only: corrections arrive as reversing entries. */
207
+ /**
208
+ * The raw ledger. Append-only: corrections arrive as reversing entries.
209
+ *
210
+ * Non-admins see only the entries that touch their own addresses, and
211
+ * within such an entry only their own postings plus the vault-level ones.
212
+ * The postings of a page therefore need not sum to zero, even though every
213
+ * complete entry does.
214
+ */
51
215
  entries(
52
216
  params: PageParams & {
217
+ /**
218
+ * Entries with a posting on this address. For a non-admin it narrows
219
+ * WITHIN their own addresses: somebody else's id answers an empty page,
220
+ * not a 403.
221
+ */
53
222
  addressId?: string
54
- /** Kind of posting: `reserve`, `confirm`, `release`, `topup`, `adjustment`. */
55
- entryType?: string
56
- operationType?: string
223
+ /** Either spelling of the stage — see `LedgerEntryType`. */
224
+ entryType?: LedgerEntryType | 'reserve' | 'confirm' | 'release' | undefined
225
+ operationType?: 'deposit' | 'transfer' | undefined
226
+ /** The transfer or deposit id. Only applied together with `operationType`. */
57
227
  operationId?: string
58
228
  token?: string
59
229
  tokenId?: string
60
230
  from?: string
61
231
  to?: string
62
232
  } = {},
63
- ): Promise<{ items: LedgerEntry[]; nextCursor: string | null }> {
64
- return this.#http.request('GET', `/api/v1/vaults/${this.#vault}/ledger/entries`, {
65
- query: { ...params },
66
- })
233
+ ): Promise<LedgerEntriesPage> {
234
+ return this.#http.request<LedgerEntriesPage>(
235
+ 'GET',
236
+ `/api/v1/vaults/${this.#vault}/ledger/entries`,
237
+ { query: { ...params } },
238
+ )
67
239
  }
68
240
  }
@@ -2,6 +2,9 @@ import { seg, type HttpClient } from '../core/http.js'
2
2
  import { toGated, type Gated } from '../core/gated.js'
3
3
  import type { PageParams } from '../core/pagination.js'
4
4
  import type {
5
+ AddOperationCategoryApproverBody,
6
+ AddOperationCategoryFieldBody,
7
+ AddOperationCategoryGrantBody,
5
8
  ApprovalRequestCreated,
6
9
  AssignCategoryBody,
7
10
  EditAssignmentBody,
@@ -9,13 +12,27 @@ import type {
9
12
  CreateOperationCategoryBody,
10
13
  OperationCategoriesPage,
11
14
  OperationCategory,
15
+ OperationCategoryApprover,
12
16
  OperationCategoryAssignment,
13
17
  OperationCategoryAssignmentVersion,
18
+ OperationCategoryField,
19
+ OperationCategoryGrant,
20
+ OperationType as GeneratedOperationType,
14
21
  PatchOperationCategoryBody,
22
+ PatchOperationCategoryFieldBody,
15
23
  } from '../generated/types.js'
16
24
 
17
- /** Operation kind in a labelling path: `transfer`, `deposit`, … */
18
- export type OperationType = string
25
+ /**
26
+ * Operation kind in a labelling path. Exactly two values exist.
27
+ *
28
+ * A strict alias, not `string`. Because `src/index.ts` re-exports this name
29
+ * explicitly, and an explicit re-export beats `export type *`, the old
30
+ * `= string` was what consumers actually got for a schema that eight
31
+ * operations reference by name — so a misspelled kind compiled here and came
32
+ * back as a 400 from production instead. Every endpoint that takes the kind
33
+ * as a path segment refuses anything but these two outright.
34
+ */
35
+ export type OperationType = GeneratedOperationType
19
36
 
20
37
  /**
21
38
  * Configurable labelling of operations.
@@ -74,13 +91,32 @@ export class CategoriesResource {
74
91
  return this.#http.request<OperationCategory>('GET', `${this.#catalogue}/${seg(categoryId)}`)
75
92
  }
76
93
 
77
- /** Categories this person may apply to this operation, with their fields. */
78
- available(type: OperationType, operationId: string): Promise<unknown> {
94
+ /**
95
+ * Categories this person may apply to this operation, with their fields.
96
+ *
97
+ * This is the endpoint to build a labelling form from, not `list()`: it
98
+ * accounts for grants and for access to the operation's own address, and it
99
+ * leaves out archived fields. `grants` and `approvers` never come back here
100
+ * — that is access configuration, and only an administrator sees it.
101
+ */
102
+ available(
103
+ type: OperationType,
104
+ operationId: string,
105
+ ): Promise<{ items: OperationCategory[] }> {
79
106
  return this.#http.request('GET', `${this.#assignments(type, operationId)}/available`)
80
107
  }
81
108
 
82
- /** What is currently applied to an operation. */
83
- assignments(type: OperationType, operationId: string): Promise<unknown> {
109
+ /**
110
+ * What is currently applied to an operation.
111
+ *
112
+ * Only the assignments whose values this person may see; a category they
113
+ * have no view access to is absent rather than empty. Within a visible one,
114
+ * sensitive fields come back as `null` with their keys in `redactedFields`.
115
+ */
116
+ assignments(
117
+ type: OperationType,
118
+ operationId: string,
119
+ ): Promise<{ items: OperationCategoryAssignment[] }> {
84
120
  return this.#http.request('GET', this.#assignments(type, operationId))
85
121
  }
86
122
 
@@ -129,11 +165,12 @@ export class CategoriesResource {
129
165
  return toGated<OperationCategoryAssignment, ApprovalRequestCreated>(res.status, res.data)
130
166
  }
131
167
 
168
+ /** Soft: the assignment goes to `removed` and its history survives. */
132
169
  removeAssignment(
133
170
  type: OperationType,
134
171
  operationId: string,
135
172
  assignmentId: string,
136
- ): Promise<unknown> {
173
+ ): Promise<{ removed: boolean }> {
137
174
  return this.#http.request(
138
175
  'DELETE',
139
176
  `${this.#assignments(type, operationId)}/${seg(assignmentId)}`,
@@ -166,12 +203,27 @@ export class CategoriesResource {
166
203
 
167
204
  // --- administrator only -------------------------------------------------
168
205
 
169
- /** Creates a category. Administrator only. */
206
+ /**
207
+ * Creates a category. Administrator only.
208
+ *
209
+ * `viewScope`, `assignScope` and `editScope` say what the category opens to
210
+ * people WITHOUT a grant, and each takes one of two values: `owner_only` —
211
+ * nothing beyond access through the operation's own address and the
212
+ * category's grants — or `vault_members`, every member of the vault.
213
+ *
214
+ * The API still accepts a third spelling on input, `restricted`, as an
215
+ * alias of `owner_only`: it is stored as `owner_only` and never comes back
216
+ * in an answer. `CategoryAccessScope` leaves it out on purpose, so it does
217
+ * not compile here — send `owner_only`.
218
+ */
170
219
  create(input: CreateOperationCategoryBody): Promise<OperationCategory> {
171
220
  return this.#http.request<OperationCategory>('POST', this.#catalogue, { body: input })
172
221
  }
173
222
 
174
- /** Administrator only. `key` cannot be changed. */
223
+ /**
224
+ * Administrator only. `key` cannot be changed. The three scopes take the
225
+ * same two values as on `create`.
226
+ */
175
227
  update(categoryId: string, input: PatchOperationCategoryBody): Promise<OperationCategory> {
176
228
  return this.#http.request<OperationCategory>(
177
229
  'PATCH',
@@ -180,16 +232,42 @@ export class CategoriesResource {
180
232
  )
181
233
  }
182
234
 
183
- /** Administrator only. A field's `key` and `type` are fixed once created. */
184
- addField(categoryId: string, input: unknown): Promise<unknown> {
185
- return this.#http.request('POST', `${this.#catalogue}/${seg(categoryId)}/fields`, {
186
- body: input,
187
- })
235
+ /**
236
+ * Administrator only. A field's `key` and `type` are fixed once created.
237
+ *
238
+ * `currencyFieldKey` is only for `type: 'currency'` and must name a sibling
239
+ * field of this same category that holds the ticker; the server refuses a
240
+ * dangling or wrongly typed link with 400 `CATEGORY_FIELD_LINK`.
241
+ */
242
+ addField(
243
+ categoryId: string,
244
+ input: AddOperationCategoryFieldBody,
245
+ ): Promise<OperationCategoryField> {
246
+ return this.#http.request<OperationCategoryField>(
247
+ 'POST',
248
+ `${this.#catalogue}/${seg(categoryId)}/fields`,
249
+ { body: input },
250
+ )
188
251
  }
189
252
 
190
- /** Administrator only. */
191
- updateField(categoryId: string, fieldId: string, input: unknown): Promise<unknown> {
192
- return this.#http.request(
253
+ /**
254
+ * Administrator only.
255
+ *
256
+ * `key` and `type` are absent from the body on purpose — they are
257
+ * immutable, since stored values, reports and snapshots are keyed by them;
258
+ * `scale` is not patchable either, so a field's precision is decided once.
259
+ * For `select`, `optionsJson` is the FULL new list and options may only be
260
+ * added. `archived: true` is how a field is retired, because a field with
261
+ * history cannot be deleted. Passing `currencyFieldKey: null` drops the
262
+ * amount↔ticker link, after which reports go back to adding different
263
+ * currencies into one meaningless total.
264
+ */
265
+ updateField(
266
+ categoryId: string,
267
+ fieldId: string,
268
+ input: PatchOperationCategoryFieldBody,
269
+ ): Promise<OperationCategoryField> {
270
+ return this.#http.request<OperationCategoryField>(
193
271
  'PATCH',
194
272
  `${this.#catalogue}/${seg(categoryId)}/fields/${seg(fieldId)}`,
195
273
  { body: input },
@@ -197,29 +275,46 @@ export class CategoriesResource {
197
275
  }
198
276
 
199
277
  /** Administrator only. Who approves edits gated by `approvalScope`. */
200
- addApprover(categoryId: string, input: unknown): Promise<unknown> {
201
- return this.#http.request('POST', `${this.#catalogue}/${seg(categoryId)}/approvers`, {
202
- body: input,
203
- })
278
+ addApprover(
279
+ categoryId: string,
280
+ input: AddOperationCategoryApproverBody,
281
+ ): Promise<OperationCategoryApprover> {
282
+ return this.#http.request<OperationCategoryApprover>(
283
+ 'POST',
284
+ `${this.#catalogue}/${seg(categoryId)}/approvers`,
285
+ { body: input },
286
+ )
204
287
  }
205
288
 
206
- /** Administrator only. */
207
- removeApprover(categoryId: string, approverId: string): Promise<unknown> {
289
+ /** Administrator only. `deleted: false` means there was nothing to remove. */
290
+ removeApprover(categoryId: string, approverId: string): Promise<{ deleted: boolean }> {
208
291
  return this.#http.request(
209
292
  'DELETE',
210
293
  `${this.#catalogue}/${seg(categoryId)}/approvers/${seg(approverId)}`,
211
294
  )
212
295
  }
213
296
 
214
- /** Administrator only. Who may see or edit values of this category. */
215
- addGrant(categoryId: string, input: unknown): Promise<unknown> {
216
- return this.#http.request('POST', `${this.#catalogue}/${seg(categoryId)}/grants`, {
217
- body: input,
218
- })
297
+ /**
298
+ * Administrator only. Who may see or edit values of this category.
299
+ *
300
+ * Idempotent, like every grant in this API: calling it again for the same
301
+ * subject updates the rights and revives a revoked grant, so there is no
302
+ * "already exists" error to handle. `canEdit` implies `canView` — the
303
+ * server fills it in, because the other combination is unenforceable.
304
+ */
305
+ addGrant(
306
+ categoryId: string,
307
+ input: AddOperationCategoryGrantBody,
308
+ ): Promise<OperationCategoryGrant> {
309
+ return this.#http.request<OperationCategoryGrant>(
310
+ 'POST',
311
+ `${this.#catalogue}/${seg(categoryId)}/grants`,
312
+ { body: input },
313
+ )
219
314
  }
220
315
 
221
- /** Administrator only. */
222
- removeGrant(categoryId: string, grantId: string): Promise<unknown> {
316
+ /** Administrator only. Soft revoke; `revoked: false` means no such grant. */
317
+ removeGrant(categoryId: string, grantId: string): Promise<{ revoked: boolean }> {
223
318
  return this.#http.request(
224
319
  'DELETE',
225
320
  `${this.#catalogue}/${seg(categoryId)}/grants/${seg(grantId)}`,
@@ -110,15 +110,23 @@ export class CounterpartiesResource {
110
110
  return toGated<CounterpartyAddressCreated, ApprovalRequestCreated>(res.status, res.data)
111
111
  }
112
112
 
113
+ /**
114
+ * Narrows any allow-list built on this counterparty, so it is gated like
115
+ * the rest — under the `counterparty_update` policy, not one of its own.
116
+ *
117
+ * Operations already attributed to this address KEEP the counterparty: at
118
+ * the time they happened the address did belong to it, and that is a
119
+ * historical fact. Only new operations stop matching.
120
+ */
113
121
  async removeAddress(
114
122
  counterpartyId: string,
115
123
  addressId: string,
116
- ): Promise<Gated<unknown, ApprovalRequestCreated>> {
124
+ ): Promise<Gated<{ deleted: true }, ApprovalRequestCreated>> {
117
125
  const res = await this.#http.requestRaw<unknown>(
118
126
  'DELETE',
119
127
  `${this.#base}/${seg(counterpartyId)}/addresses/${seg(addressId)}`,
120
128
  )
121
- return toGated<unknown, ApprovalRequestCreated>(res.status, res.data)
129
+ return toGated<{ deleted: true }, ApprovalRequestCreated>(res.status, res.data)
122
130
  }
123
131
  }
124
132
 
@@ -173,24 +181,33 @@ export class CounterpartyGroupsResource {
173
181
  return toGated<CounterpartyGroup, ApprovalRequestCreated>(res.status, res.data)
174
182
  }
175
183
 
184
+ /**
185
+ * WIDENS any allow-list built on this group, and does so the moment it
186
+ * applies — which is why it is gated by its own policy
187
+ * (`counterparty_group_add`) rather than riding on the group's update.
188
+ */
176
189
  async addMember(
177
190
  groupId: string,
178
191
  counterpartyId: string,
179
- ): Promise<Gated<unknown, ApprovalRequestCreated>> {
192
+ ): Promise<Gated<{ added: true }, ApprovalRequestCreated>> {
180
193
  const res = await this.#http.requestRaw<unknown>('POST', `${this.#base}/${seg(groupId)}/members`, {
181
194
  body: { counterpartyId },
182
195
  })
183
- return toGated<unknown, ApprovalRequestCreated>(res.status, res.data)
196
+ return toGated<{ added: true }, ApprovalRequestCreated>(res.status, res.data)
184
197
  }
185
198
 
199
+ /**
200
+ * Payments to this counterparty under the group's allow-list stop at once.
201
+ * `removed: false` means it was not in the group — not a failure.
202
+ */
186
203
  async removeMember(
187
204
  groupId: string,
188
205
  counterpartyId: string,
189
- ): Promise<Gated<unknown, ApprovalRequestCreated>> {
206
+ ): Promise<Gated<{ removed: boolean }, ApprovalRequestCreated>> {
190
207
  const res = await this.#http.requestRaw<unknown>(
191
208
  'DELETE',
192
209
  `${this.#base}/${seg(groupId)}/members/${seg(counterpartyId)}`,
193
210
  )
194
- return toGated<unknown, ApprovalRequestCreated>(res.status, res.data)
211
+ return toGated<{ removed: boolean }, ApprovalRequestCreated>(res.status, res.data)
195
212
  }
196
213
  }
@@ -1,14 +1,17 @@
1
1
  import { seg, type HttpClient } from '../core/http.js'
2
2
  import type {
3
3
  AddFolderAddressBody,
4
+ AddressAccessGrant,
4
5
  AddressGroup,
5
6
  AddressGroupDetail,
6
7
  AddressGroupsList,
8
+ AddressSharesList,
7
9
  CreateAddressGroupBody,
8
10
  GrantGroupBody,
9
11
  ParticipantsResponse,
10
12
  PatchAddressGroupBody,
11
13
  PatchGroupGrantBody,
14
+ ShareGrantCreated,
12
15
  } from '../generated/types.js'
13
16
 
14
17
  /**
@@ -62,39 +65,92 @@ export class FoldersResource {
62
65
  })
63
66
  }
64
67
 
65
- delete(folderId: string): Promise<unknown> {
66
- return this.#http.request('DELETE', `${this.#base}/${seg(folderId)}`)
68
+ /** Deleting takes the membership rows with it, and the grants on the folder. */
69
+ delete(folderId: string): Promise<{ deleted: boolean }> {
70
+ return this.#http.request<{ deleted: boolean }>('DELETE', `${this.#base}/${seg(folderId)}`)
67
71
  }
68
72
 
69
- addAddress(folderId: string, input: AddFolderAddressBody): Promise<unknown> {
70
- return this.#http.request('POST', `${this.#base}/${seg(folderId)}/members`, { body: input })
73
+ addAddress(folderId: string, input: AddFolderAddressBody): Promise<{ added: boolean }> {
74
+ return this.#http.request<{ added: boolean }>(
75
+ 'POST',
76
+ `${this.#base}/${seg(folderId)}/members`,
77
+ { body: input },
78
+ )
71
79
  }
72
80
 
73
- removeAddress(folderId: string, addressId: string): Promise<unknown> {
74
- return this.#http.request(
81
+ /**
82
+ * Taking an address out ends access that came FROM THE FOLDER. Grants on
83
+ * the address itself are untouched — they are a separate relationship.
84
+ */
85
+ removeAddress(folderId: string, addressId: string): Promise<{ removed: boolean }> {
86
+ return this.#http.request<{ removed: boolean }>(
75
87
  'DELETE',
76
88
  `${this.#base}/${seg(folderId)}/members/${seg(addressId)}`,
77
89
  )
78
90
  }
79
91
 
80
- /** Grants on the folder itself — everything in it becomes reachable. */
81
- grants(folderId: string): Promise<unknown> {
82
- return this.#http.request('GET', `${this.#base}/${seg(folderId)}/grants`)
92
+ /**
93
+ * Grants on the folder itself — everything in it becomes reachable,
94
+ * including addresses added later.
95
+ *
96
+ * The same shape as the grants on a single address, and each entry carries
97
+ * `role`: do not rebuild it from the flags on the client, or you end up
98
+ * with a second translation that diverges from the first on the next right
99
+ * that is added.
100
+ *
101
+ * Owners of the addresses inside are NOT listed — their access comes from
102
+ * ownership, not from a grant. For people rather than grants, ask
103
+ * `participants()`.
104
+ */
105
+ grants(folderId: string): Promise<AddressSharesList> {
106
+ return this.#http.request<AddressSharesList>(
107
+ 'GET',
108
+ `${this.#base}/${seg(folderId)}/grants`,
109
+ )
83
110
  }
84
111
 
85
- /** Idempotent, and accepts several subjects at once. Check each result. */
86
- grant(folderId: string, input: GrantGroupBody): Promise<unknown> {
87
- return this.#http.request('POST', `${this.#base}/${seg(folderId)}/grants`, { body: input })
112
+ /**
113
+ * Idempotent: repeating it for the same pair updates the rights and revives
114
+ * a revoked grant — no 409 to catch, no read-then-PATCH dance.
115
+ *
116
+ * With `subjects[]` it answers a batch (`results[]`, one entry per subject,
117
+ * each independent); with a single subject it answers the grant itself,
118
+ * hence the union. The all-failed case behaves as it does on
119
+ * `sharing.share` — 400 carrying the same `results[]` body
120
+ * (`ShareBatchAllRejected`), which the SDK surfaces as a `CoreError` with
121
+ * the body in `details`.
122
+ */
123
+ grant(folderId: string, input: GrantGroupBody): Promise<ShareGrantCreated> {
124
+ return this.#http.request<ShareGrantCreated>(
125
+ 'POST',
126
+ `${this.#base}/${seg(folderId)}/grants`,
127
+ { body: input },
128
+ )
88
129
  }
89
130
 
90
- updateGrant(folderId: string, grantId: string, input: PatchGroupGrantBody): Promise<unknown> {
91
- return this.#http.request('PATCH', `${this.#base}/${seg(folderId)}/grants/${seg(grantId)}`, {
92
- body: input,
93
- })
131
+ /**
132
+ * `role` sets view and transfer together; the individual flags are the
133
+ * older way in. `canCreateAddress` is not part of either role and is
134
+ * changed on its own.
135
+ */
136
+ updateGrant(
137
+ folderId: string,
138
+ grantId: string,
139
+ input: PatchGroupGrantBody,
140
+ ): Promise<AddressAccessGrant> {
141
+ return this.#http.request<AddressAccessGrant>(
142
+ 'PATCH',
143
+ `${this.#base}/${seg(folderId)}/grants/${seg(grantId)}`,
144
+ { body: input },
145
+ )
94
146
  }
95
147
 
96
- revokeGrant(folderId: string, grantId: string): Promise<unknown> {
97
- return this.#http.request('DELETE', `${this.#base}/${seg(folderId)}/grants/${seg(grantId)}`)
148
+ /** Soft: the row stays as `revoked` for the audit trail. Effective at once. */
149
+ revokeGrant(folderId: string, grantId: string): Promise<{ revoked: boolean }> {
150
+ return this.#http.request<{ revoked: boolean }>(
151
+ 'DELETE',
152
+ `${this.#base}/${seg(folderId)}/grants/${seg(grantId)}`,
153
+ )
98
154
  }
99
155
 
100
156
  /** People who reach the folder, with the source of each person's access. */