@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
package/src/index.ts CHANGED
@@ -11,7 +11,20 @@
11
11
  * Signing up is possible only on an administrator's invitation.
12
12
  */
13
13
 
14
- export { CoreClient, type VaultScope, type VaultSummary, type Profile } from './client.js'
14
+ export { CoreClient, type VaultScope } from './client.js'
15
+
16
+ /**
17
+ * Kept so that existing imports keep resolving. Both are now strict aliases
18
+ * of generated schemas — `VaultSummary` of `VaultListItem`, `Profile` of
19
+ * `UserProfile` — and new code should name those instead.
20
+ *
21
+ * They are listed apart from `CoreClient` on purpose: an explicit re-export
22
+ * SHADOWS the same name coming from `export type * from './generated/types'`
23
+ * below, so a hand-written type parked in this list quietly becomes the one
24
+ * consumers get. Anything added here has to be identical to the schema it
25
+ * covers, or carry a name the schemas do not use.
26
+ */
27
+ export type { VaultSummary, Profile } from './client.js'
15
28
 
16
29
  export {
17
30
  HttpClient,
@@ -76,13 +89,29 @@ export {
76
89
  } from './resources/addresses.js'
77
90
 
78
91
  export { TransfersResource, type VoteInput } from './resources/transfers.js'
79
- export { HistoryResource, type HistoryParams } from './resources/history.js'
92
+ export {
93
+ HistoryResource,
94
+ type HistoryParams,
95
+ type HistoryStatusFilter,
96
+ } from './resources/history.js'
80
97
  export { MeResource } from './resources/me.js'
81
98
  export { SharingResource } from './resources/sharing.js'
82
99
  export { FoldersResource } from './resources/folders.js'
83
100
  export { MembersResource } from './resources/members.js'
84
- export { RegistriesResource } from './resources/registries.js'
101
+ export { RegistriesResource, type AmlScore } from './resources/registries.js'
85
102
  export { CategoriesResource, type OperationType } from './resources/categories.js'
103
+ /**
104
+ * `ReportSnapshotListItem` and `ReportSnapshotDetail` are also the names of
105
+ * generated schemas, and listing them here shadows those. That is safe only
106
+ * because both are strict aliases (`export type X = GeneratedX`) — see the
107
+ * note on `VaultSummary` above, and `test/nameShadowing.test.ts`, which fails
108
+ * the moment one of them stops being identical. The same goes for
109
+ * `LedgerPosting` in the balances block below.
110
+ *
111
+ * The report schemas themselves — `TransactionActivityReport`,
112
+ * `BalancePositionReport`, `AnyReport` and the rest of what `ReportResult`
113
+ * maps to — are not listed: they reach consumers through `export type *`.
114
+ */
86
115
  export {
87
116
  ReportsResource,
88
117
  type ReportType,
@@ -90,12 +119,40 @@ export {
90
119
  type ReportParams,
91
120
  type ReportResult,
92
121
  type ReportSnapshotListItem,
122
+ type ReportSnapshotDetail,
123
+ type DrilldownQuery,
124
+ type DrilldownFilters,
125
+ type DrilldownSelection,
93
126
  } from './resources/reports.js'
94
- export { AdminResource } from './resources/admin.js'
127
+ export {
128
+ AdminResource,
129
+ type PlatformRole,
130
+ type PolicyRow,
131
+ type PolicyRowWithGroup,
132
+ type VaultPoliciesParams,
133
+ } from './resources/admin.js'
95
134
  export { AuthResource } from './resources/auth.js'
96
- export { OperationsResource, type OperationKind } from './resources/operations.js'
97
- export { ApprovalsResource, type ListApprovalsParams } from './resources/approvals.js'
98
- export { BalancesResource, type BalancesParams } from './resources/balances.js'
135
+ export {
136
+ OperationsResource,
137
+ type OperationKind,
138
+ type ListRelationsParams,
139
+ } from './resources/operations.js'
140
+ export {
141
+ ApprovalsResource,
142
+ type ListApprovalsParams,
143
+ type ApprovalRequestStatus,
144
+ } from './resources/approvals.js'
145
+ export {
146
+ BalancesResource,
147
+ type BalancesParams,
148
+ type BalancesAsOfParams,
149
+ type BalancesAsOfSummary,
150
+ type BalancesAsOfWithAccounts,
151
+ type LedgerEntryItem,
152
+ type LedgerEntryType,
153
+ type LedgerPosting,
154
+ type LedgerPurpose,
155
+ } from './resources/balances.js'
99
156
  export {
100
157
  CounterpartiesResource,
101
158
  CounterpartyGroupsResource,
@@ -1,20 +1,29 @@
1
1
  import { seg, type HttpClient } from '../core/http.js'
2
2
  import type { Page, PageParams } from '../core/pagination.js'
3
3
  import { toGated, type Gated } from '../core/gated.js'
4
-
5
- /** Returned when creating an address required approval instead of running. */
6
- export interface PendingApproval {
7
- status: string
8
- approvalRequestId: string
9
- approvalThreshold: number
10
- }
11
4
  import type { Address, AddressDetail } from './types.js'
12
5
  import type {
13
6
  CreateAddressBody,
14
7
  CreateAddressResult,
15
8
  PatchAddressBody,
9
+ PendingApproval as GeneratedPendingApproval,
16
10
  } from '../generated/types.js'
17
11
 
12
+ /**
13
+ * Returned when creating an address required approval instead of running.
14
+ *
15
+ * A strict alias, not a hand-written copy. The copy declared `status: string`
16
+ * and, because `src/index.ts` re-exports this name explicitly, it won the
17
+ * contest against `export type *` and became what consumers saw — a wider
18
+ * type than the API can produce, where the only value is the literal
19
+ * `"awaiting_approval"`.
20
+ *
21
+ * Do not reach for `status` to tell the two outcomes apart: `Gated` already
22
+ * distinguishes them by `outcome`, and this branch is only ever reached when
23
+ * nothing was created.
24
+ */
25
+ export type PendingApproval = GeneratedPendingApproval
26
+
18
27
  export interface ListAddressesParams extends PageParams {
19
28
  /** Token code: `usdt`, `a7a5`. */
20
29
  token?: string | undefined
@@ -61,7 +70,14 @@ export class AddressesResource {
61
70
  return toGated<CreateAddressResult, PendingApproval>(res.status, res.data)
62
71
  }
63
72
 
64
- /** Metadata only: this touches neither money nor the derivation path. */
73
+ /**
74
+ * Metadata only: this touches neither money nor the derivation path.
75
+ *
76
+ * Answers with the same row as the listing — `owner`, `access` and
77
+ * `sharing` included — so it can replace the address in a list without a
78
+ * refetch. Recent operations and `shares`, which `get()` carries, are not
79
+ * in it.
80
+ */
65
81
  update(addressId: string, input: PatchAddressBody): Promise<Address> {
66
82
  return this.#http.request<Address>('PATCH', `${this.#base}/${seg(addressId)}`, { body: input })
67
83
  }
@@ -1,8 +1,17 @@
1
1
  import { seg, type HttpClient } from '../core/http.js'
2
2
  import type { PageParams } from '../core/pagination.js'
3
3
  import type {
4
+ AcceptDebugBody,
5
+ AcceptResult,
6
+ ActionType,
4
7
  CreateInvitationBody,
8
+ Effect,
9
+ Invitation,
10
+ InvitationListItem,
11
+ InvitationPublic,
12
+ Membership,
5
13
  PolicyGroup,
14
+ PolicyGroupWithCounts,
6
15
  PolicyTableItem,
7
16
  PolicyWithRules,
8
17
  CreateSeatBody,
@@ -17,10 +26,49 @@ import type {
17
26
  PolicyDetail,
18
27
  PolicyGroupDetail,
19
28
  PolicyVersionList,
29
+ UserGroupItem,
20
30
  VaultPoliciesPage,
21
31
  VisibilityGrant,
22
32
  } from '../generated/types.js'
23
33
 
34
+ /**
35
+ * A policy as the two LISTING endpoints return it. A strict alias of the
36
+ * generated `PolicyTableItem`, kept under its earlier name.
37
+ *
38
+ * There is NO `conditionJson` and no `approvalJson` here: neither listing
39
+ * selects them, so a row cannot tell you whether a policy is conditional —
40
+ * and treating the absence as "applies always" is the opposite of what a
41
+ * condition usually says. Load one policy (`policies.get`) when you need the
42
+ * rule itself; the vault-scoped page (`vault().policies`) does carry both
43
+ * fields.
44
+ */
45
+ export type PolicyRow = PolicyTableItem
46
+
47
+ /** Filters of the vault-scoped policy page (`vault().policies`). */
48
+ export interface VaultPoliciesParams extends PageParams {
49
+ actionType?: ActionType | undefined
50
+ /**
51
+ * `requireApproval` is accepted here and nowhere else. A policy's own
52
+ * effect has been `allow | block` only since approval became a property of
53
+ * an allowing policy rather than an effect of its own, so this third value
54
+ * exists for rows written before that change — not for new ones.
55
+ */
56
+ effect?: Effect | 'requireApproval' | undefined
57
+ enabled?: boolean | undefined
58
+ }
59
+
60
+ /** A policy from the flat vault listing, which also names its owner group. */
61
+ export interface PolicyRowWithGroup extends PolicyRow {
62
+ groupId: string
63
+ group: {
64
+ id: string
65
+ code: string
66
+ name: string
67
+ color: string | null
68
+ emoji: string | null
69
+ }
70
+ }
71
+
24
72
  /**
25
73
  * Administrative endpoints.
26
74
  *
@@ -33,6 +81,12 @@ import type {
33
81
  * this OBJECT" — grants — lives with the resources it belongs to
34
82
  * (`sharing`, `folders`, `categories`).
35
83
  */
84
+ /**
85
+ * The PLATFORM role, from the spec's own enum. Not a role inside a vault —
86
+ * a platform `admin` sees every vault while belonging to none.
87
+ */
88
+ export type PlatformRole = 'admin' | 'user'
89
+
36
90
  export class AdminResource {
37
91
  readonly #http: HttpClient
38
92
 
@@ -63,15 +117,26 @@ export class AdminResource {
63
117
  /**
64
118
  * `vaultId` is REQUIRED — the endpoint lists policies of one vault and
65
119
  * answers 400 without it. It is also NOT paginated: everything comes
66
- * back in one array.
120
+ * back in one array, sorted group then code.
121
+ *
122
+ * `actionType` is spelled out rather than left as a string because the
123
+ * API does not validate it here: an unrecognised value is passed to the
124
+ * query as-is and comes back as an EMPTY list, which reads like "this
125
+ * vault has no transfer policies" instead of "you misspelled it". The
126
+ * vault-scoped twin (`vault().policies`) declares the same ten values as
127
+ * an enum, so one spelling serves both.
128
+ *
129
+ * `effect` is `allow | block` only. Approval stopped being an effect when
130
+ * it became a property of an allowing policy; the vault-scoped twin still
131
+ * accepts the historical third value, this one does not and answers 400.
67
132
  */
68
133
  list: (params: {
69
134
  vaultId: string
70
- groupId?: string
71
- actionType?: string
72
- effect?: string
73
- enabled?: boolean
74
- }) => this.#http.request<{ items: PolicyTableItem[] }>('GET', '/api/v1/policies', {
135
+ groupId?: string | undefined
136
+ actionType?: ActionType | undefined
137
+ effect?: Effect | undefined
138
+ enabled?: boolean | undefined
139
+ }) => this.#http.request<{ items: PolicyRowWithGroup[] }>('GET', '/api/v1/policies', {
75
140
  query: { ...params },
76
141
  }),
77
142
 
@@ -109,10 +174,23 @@ export class AdminResource {
109
174
  get: (groupId: string) =>
110
175
  this.#http.request<PolicyGroupDetail>('GET', `/api/v1/policy-groups/${seg(groupId)}`),
111
176
 
112
- policies: (groupId: string) =>
113
- this.#http.request('GET', `/api/v1/policy-groups/${seg(groupId)}/policies`),
177
+ /** This group's policies for a table view: every field but the rules. */
178
+ policies: (groupId: string, params: { actionType?: ActionType | undefined } = {}) =>
179
+ this.#http.request<PolicyRow[]>('GET', `/api/v1/policy-groups/${seg(groupId)}/policies`, {
180
+ query: { ...params },
181
+ }),
114
182
 
115
- /** What this group's members can reach. */
183
+ /**
184
+ * What this group's members can reach: the group, its member count, its
185
+ * policies and the three kinds of grant held by the group as a subject
186
+ * (addresses, member visibility, operation categories).
187
+ *
188
+ * Left untyped. The API declares this body inline, so there is no
189
+ * generated name to import, and it nests four different resources —
190
+ * hand-writing them here would add exactly the kind of duplicated shape
191
+ * that drifts from the API with nothing to notice it. Narrow it at the
192
+ * call site, or read the single-purpose endpoints.
193
+ */
116
194
  access: (groupId: string) =>
117
195
  this.#http.request('GET', `/api/v1/policy-groups/${seg(groupId)}/access`),
118
196
 
@@ -129,13 +207,21 @@ export class AdminResource {
129
207
  * used to drift apart, leaving people with policies but no membership.
130
208
  */
131
209
  addMember: (groupId: string, userId: string) =>
132
- this.#http.request('POST', `/api/v1/policy-groups/${seg(groupId)}/members`, {
210
+ this.#http.request<Membership>('POST', `/api/v1/policy-groups/${seg(groupId)}/members`, {
133
211
  body: { userId },
134
212
  }),
135
213
 
136
- /** Takes the MEMBERSHIP id, not the user id — a user id answers 404. */
214
+ /**
215
+ * Takes the MEMBERSHIP id, not the user id — a user id answers 404. The
216
+ * id comes from `users.policyGroups`, which returns it as `membershipId`
217
+ * for this purpose.
218
+ *
219
+ * Removing somebody from their last group does NOT remove them from the
220
+ * vault: membership is its own record, so they stay in the directory with
221
+ * no policies — able to be granted access, refused every action.
222
+ */
137
223
  removeMember: (groupId: string, membershipId: string) =>
138
- this.#http.request(
224
+ this.#http.request<{ deleted: true }>(
139
225
  'DELETE',
140
226
  `/api/v1/policy-groups/${seg(groupId)}/members/${seg(membershipId)}`,
141
227
  ),
@@ -144,27 +230,60 @@ export class AdminResource {
144
230
  // --- people -------------------------------------------------------------
145
231
 
146
232
  readonly users = {
147
- policyGroups: (userId: string) =>
148
- this.#http.request('GET', `/api/v1/users/${seg(userId)}/policy-groups`),
233
+ /**
234
+ * Every group this person is in, each with the `membershipId` that
235
+ * `policyGroups.removeMember` needs. Without `vaultId` it spans ALL
236
+ * vaults, so filter when you are rendering one vault's screen.
237
+ */
238
+ policyGroups: (userId: string, params: { vaultId?: string | undefined } = {}) =>
239
+ this.#http.request<UserGroupItem[]>('GET', `/api/v1/users/${seg(userId)}/policy-groups`, {
240
+ query: { ...params },
241
+ }),
149
242
 
150
- /** The PLATFORM role, not a role within a vault. The two are different. */
151
- setRole: (userId: string, role: string) =>
152
- this.#http.request('PATCH', `/api/v1/users/${seg(userId)}/role`, { body: { role } }),
243
+ /**
244
+ * The PLATFORM role, not a role within a vault. The two are different:
245
+ * a platform `admin` sees every vault while belonging to none, whereas a
246
+ * vault membership carries its own role.
247
+ *
248
+ * The parameter is narrowed to the two values the spec declares, which is
249
+ * worth the compile error on a privilege path: `string` here let a caller
250
+ * pass anything and find out from a 400 — after sending a role change.
251
+ * The RESPONSE keeps `role: string`, because the spec declares it bare and
252
+ * narrowing an answer we do not control would be a guess.
253
+ */
254
+ setRole: (userId: string, role: PlatformRole) =>
255
+ this.#http.request<{ id: string; email: string; role: string }>(
256
+ 'PATCH',
257
+ `/api/v1/users/${seg(userId)}/role`,
258
+ { body: { role } },
259
+ ),
153
260
  }
154
261
 
155
262
  /** Vault-scoped administrative listings and creation. */
156
263
  vault(vaultCode: string): {
157
- invitations: () => Promise<unknown>
158
- policies: (params?: Record<string, string | number | boolean>) => Promise<VaultPoliciesPage>
159
- policyGroups: () => Promise<unknown>
264
+ invitations: (params?: {
265
+ status?: 'pending' | 'accepted' | 'revoked' | undefined
266
+ }) => Promise<InvitationListItem[]>
267
+ policies: (params?: VaultPoliciesParams) => Promise<VaultPoliciesPage>
268
+ policyGroups: () => Promise<PolicyGroupWithCounts[]>
160
269
  createSeat: (input: CreateSeatBody) => Promise<SeatCreated>
161
270
  } {
162
271
  const base = `/api/v1/vaults/${seg(vaultCode)}`
163
272
  return {
164
- invitations: () => this.#http.request('GET', `${base}/invitations`),
273
+ /**
274
+ * Every invitation of the vault, newest first. The magic-link TOKEN is
275
+ * not in the listing — only `invitations.get` returns it, so that it
276
+ * cannot leak through a screenshot or a log of a list view.
277
+ */
278
+ invitations: (params = {}) =>
279
+ this.#http.request<InvitationListItem[]>('GET', `${base}/invitations`, {
280
+ query: { ...params },
281
+ }),
165
282
  policies: (params = {}) =>
166
- this.#http.request<VaultPoliciesPage>('GET', `${base}/policies`, { query: params }),
167
- policyGroups: () => this.#http.request('GET', `${base}/policy-groups`),
283
+ this.#http.request<VaultPoliciesPage>('GET', `${base}/policies`, { query: { ...params } }),
284
+ /** Groups with `_count` of their policies and members. No cursor. */
285
+ policyGroups: () =>
286
+ this.#http.request<PolicyGroupWithCounts[]>('GET', `${base}/policy-groups`),
168
287
  /**
169
288
  * Creates a "seat": an account prepared in advance, before the person's
170
289
  * email is known, handed over as a link or a QR code. The link is
@@ -177,22 +296,44 @@ export class AdminResource {
177
296
  }
178
297
 
179
298
  readonly invitations = {
180
- /** Public: lets an invited person see what they were invited to. */
299
+ /**
300
+ * Public: lets an invited person see what they were invited to — the
301
+ * vault and the groups, no token echoed back. The only endpoint here
302
+ * that needs no session, because it is what the /accept page calls.
303
+ *
304
+ * A revoked or already-accepted invitation answers 410, not 404: the
305
+ * difference is worth showing, since "this link was withdrawn" and "this
306
+ * link never existed" lead somewhere different.
307
+ */
181
308
  byToken: (token: string) =>
182
- this.#http.request('GET', `/api/v1/invitations/by-token/${seg(token)}`),
309
+ this.#http.request<InvitationPublic>(
310
+ 'GET',
311
+ `/api/v1/invitations/by-token/${seg(token)}`,
312
+ ),
183
313
 
314
+ /** The one place the magic-link token is returned. */
184
315
  get: (invitationId: string) =>
185
316
  this.#http.request<InvitationDetail>('GET', `/api/v1/invitations/${seg(invitationId)}`),
186
317
 
187
318
  create: (input: CreateInvitationBody) =>
188
319
  this.#http.request<InvitationCreated>('POST', '/api/v1/invitations', { body: input }),
189
320
 
321
+ /** Answers the invitation itself, now `revoked`. Already revoked → 400. */
190
322
  revoke: (invitationId: string) =>
191
- this.#http.request('POST', `/api/v1/invitations/${seg(invitationId)}/revoke`),
323
+ this.#http.request<Invitation>('POST', `/api/v1/invitations/${seg(invitationId)}/revoke`),
192
324
 
193
- /** Accepts an invitation without the email round-trip. For debugging. */
194
- acceptDebug: (body: unknown) =>
195
- this.#http.request('POST', '/api/v1/invitations/accept-debug', { body }),
325
+ /**
326
+ * Accepts an invitation without the email round-trip, for an account that
327
+ * already exists. For debugging and curl tests: in the real flow this
328
+ * happens inside the sign-up hook.
329
+ *
330
+ * Matches on the EMAIL in the body, not on a token — it finds the pending
331
+ * invitation for that address. No pending invitation → 404; the user
332
+ * already attached to another invitation → 400, so a repeat is refused
333
+ * rather than duplicating memberships.
334
+ */
335
+ acceptDebug: (body: AcceptDebugBody) =>
336
+ this.#http.request<AcceptResult>('POST', '/api/v1/invitations/accept-debug', { body }),
196
337
  }
197
338
 
198
339
  /**
@@ -204,15 +345,27 @@ export class AdminResource {
204
345
  * here.
205
346
  */
206
347
  visibilityGrants(vaultCode: string): {
207
- list: () => Promise<unknown>
348
+ list: (params?: {
349
+ subjectUserId?: string | undefined
350
+ subjectGroupId?: string | undefined
351
+ includeRevoked?: boolean | undefined
352
+ }) => Promise<{ items: VisibilityGrant[] }>
208
353
  create: (input: CreateVisibilityGrantBody) => Promise<VisibilityGrant>
209
- revoke: (grantId: string) => Promise<unknown>
354
+ revoke: (grantId: string) => Promise<{ revoked: true }>
210
355
  } {
211
356
  const base = `/api/v1/vaults/${seg(vaultCode)}/visibility-grants`
212
357
  return {
213
- list: () => this.#http.request('GET', base),
358
+ /**
359
+ * Active grants with the cards of subject and target group. The subject
360
+ * filters answer "what is open to this person or group", which is what
361
+ * their detail screen needs; `includeRevoked` adds the soft-deleted
362
+ * rows, the only way to see what access was withdrawn and when.
363
+ */
364
+ list: (params = {}) =>
365
+ this.#http.request<{ items: VisibilityGrant[] }>('GET', base, { query: { ...params } }),
214
366
  create: (input) => this.#http.request<VisibilityGrant>('POST', base, { body: input }),
215
- revoke: (grantId) => this.#http.request('DELETE', `${base}/${seg(grantId)}`),
367
+ /** Soft: the row stays as `revoked`. Effective immediately. */
368
+ revoke: (grantId) => this.#http.request<{ revoked: true }>('DELETE', `${base}/${seg(grantId)}`),
216
369
  }
217
370
  }
218
371
  }
@@ -4,14 +4,30 @@ import { toGated, type Gated } from '../core/gated.js'
4
4
  import type {
5
5
  ApprovalRequestCreated,
6
6
  ApprovalRequestDetail,
7
- CompositeApplied,
8
7
  ApprovalRequestsPage,
8
+ CompositeApplied,
9
9
  CreateCompositeBody,
10
+ RequestVoteResult,
10
11
  VoteBody,
11
12
  } from '../generated/types.js'
12
13
 
14
+ /**
15
+ * Status of an approval REQUEST — and in UPPER CASE.
16
+ *
17
+ * That is the trap this type exists for: a transfer's status is lowercase
18
+ * (`awaiting_approval`, `confirmed`), a request's is not, and the two live
19
+ * side by side on the same screen. `status: 'pending'` used to compile and
20
+ * answer 400.
21
+ *
22
+ * `APPROVED` is about the VOTES, not about the outcome. "Approved but it
23
+ * failed to apply" is still `APPROVED`, and the difference shows up in
24
+ * `applyError` and `resolvedResultId` on the vote's answer — so a listing
25
+ * filtered to `APPROVED` is not a listing of things that happened.
26
+ */
27
+ export type ApprovalRequestStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'CANCELLED'
28
+
13
29
  export interface ListApprovalsParams extends PageParams {
14
- status?: string | undefined
30
+ status?: ApprovalRequestStatus | undefined
15
31
  action?: string | undefined
16
32
  /** Requests I STARTED. Not the same as the ones waiting for me. */
17
33
  mine?: boolean | undefined
@@ -52,15 +68,45 @@ export class ApprovalsResource {
52
68
  return this.#http.request<ApprovalRequestDetail>('GET', `${this.#base}/${seg(requestId)}`)
53
69
  }
54
70
 
55
- vote(requestId: string, input: VoteBody): Promise<unknown> {
56
- return this.#http.request('POST', `${this.#base}/${seg(requestId)}/approvals`, { body: input })
71
+ /**
72
+ * Typed from `RequestVoteResult`, which the spec declares as a component but
73
+ * never `$ref`s — the operation inlines its 200 instead. The two were
74
+ * compared key by key and type by type and are the same schema; the only
75
+ * difference is an `additionalProperties: false` that does not reach the
76
+ * TypeScript shape. Worth saying, because a component nothing references
77
+ * usually means the opposite: an orphan left behind, which is what the
78
+ * `Address` schema turned out to be.
79
+ *
80
+ * Read `finalized` with `applyError`: a vote can finalize a request AND
81
+ * fail to apply it, and then the money has not moved.
82
+ */
83
+ vote(requestId: string, input: VoteBody): Promise<RequestVoteResult> {
84
+ return this.#http.request<RequestVoteResult>(
85
+ 'POST',
86
+ `${this.#base}/${seg(requestId)}/approvals`,
87
+ { body: input },
88
+ )
57
89
  }
58
90
 
59
- /** Cancels your own request and releases whatever it reserved. */
60
- cancel(requestId: string, reason?: string): Promise<unknown> {
61
- return this.#http.request('POST', `${this.#base}/${seg(requestId)}/cancel`, {
62
- body: reason === undefined ? {} : { reason },
63
- })
91
+ /**
92
+ * Cancels your own request and releases whatever it reserved.
93
+ *
94
+ * Only while the request is `PENDING`, and only as its initiator or as an
95
+ * admin — the admin case exists so a request whose initiator is gone can
96
+ * still be cleared instead of holding the reserve forever.
97
+ *
98
+ * @param reason IGNORED BY THE SERVER, and no longer sent. The endpoint
99
+ * declares no request body and the handler never reads one, so the reason
100
+ * reached nothing: not the cancellation event, not the audit trail. It
101
+ * stays in the signature only so that existing callers keep compiling —
102
+ * drop it from your call, and record the reason wherever you need it to
103
+ * actually survive.
104
+ */
105
+ cancel(requestId: string, reason?: string): Promise<{ status: 'CANCELLED' }> {
106
+ return this.#http.request<{ status: 'CANCELLED' }>(
107
+ 'POST',
108
+ `${this.#base}/${seg(requestId)}/cancel`,
109
+ )
64
110
  }
65
111
 
66
112
  /**