@robosystems/client 0.2.49 → 0.3.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 (191) hide show
  1. package/extensions/InvestorClient.d.ts +98 -0
  2. package/extensions/InvestorClient.js +223 -0
  3. package/extensions/InvestorClient.ts +508 -0
  4. package/extensions/LedgerClient.d.ts +190 -309
  5. package/extensions/LedgerClient.js +268 -587
  6. package/extensions/LedgerClient.ts +784 -903
  7. package/extensions/ReportClient.d.ts +82 -113
  8. package/extensions/ReportClient.js +124 -211
  9. package/extensions/ReportClient.ts +305 -358
  10. package/extensions/config.d.ts +19 -0
  11. package/extensions/config.js +1 -1
  12. package/extensions/config.ts +22 -2
  13. package/extensions/graphql/client.d.ts +82 -0
  14. package/extensions/graphql/client.js +149 -0
  15. package/extensions/graphql/client.ts +189 -0
  16. package/extensions/graphql/generated/graphql.d.ts +1786 -0
  17. package/extensions/graphql/generated/graphql.js +2671 -0
  18. package/extensions/graphql/generated/graphql.ts +4491 -0
  19. package/extensions/graphql/queries/investor/holdings.d.ts +8 -0
  20. package/extensions/graphql/queries/investor/holdings.js +36 -0
  21. package/extensions/graphql/queries/investor/holdings.ts +34 -0
  22. package/extensions/graphql/queries/investor/portfolio.d.ts +4 -0
  23. package/extensions/graphql/queries/investor/portfolio.js +21 -0
  24. package/extensions/graphql/queries/investor/portfolio.ts +19 -0
  25. package/extensions/graphql/queries/investor/portfolios.d.ts +4 -0
  26. package/extensions/graphql/queries/investor/portfolios.js +29 -0
  27. package/extensions/graphql/queries/investor/portfolios.ts +27 -0
  28. package/extensions/graphql/queries/investor/position.d.ts +4 -0
  29. package/extensions/graphql/queries/investor/position.js +33 -0
  30. package/extensions/graphql/queries/investor/position.ts +31 -0
  31. package/extensions/graphql/queries/investor/positions.d.ts +8 -0
  32. package/extensions/graphql/queries/investor/positions.js +57 -0
  33. package/extensions/graphql/queries/investor/positions.ts +55 -0
  34. package/extensions/graphql/queries/investor/securities.d.ts +8 -0
  35. package/extensions/graphql/queries/investor/securities.js +50 -0
  36. package/extensions/graphql/queries/investor/securities.ts +48 -0
  37. package/extensions/graphql/queries/investor/security.d.ts +4 -0
  38. package/extensions/graphql/queries/investor/security.js +26 -0
  39. package/extensions/graphql/queries/investor/security.ts +24 -0
  40. package/extensions/graphql/queries/ledger/accountRollups.d.ts +7 -0
  41. package/extensions/graphql/queries/ledger/accountRollups.js +36 -0
  42. package/extensions/graphql/queries/ledger/accountRollups.ts +34 -0
  43. package/extensions/graphql/queries/ledger/accountTree.d.ts +11 -0
  44. package/extensions/graphql/queries/ledger/accountTree.js +61 -0
  45. package/extensions/graphql/queries/ledger/accountTree.ts +59 -0
  46. package/extensions/graphql/queries/ledger/accounts.d.ts +8 -0
  47. package/extensions/graphql/queries/ledger/accounts.js +45 -0
  48. package/extensions/graphql/queries/ledger/accounts.ts +43 -0
  49. package/extensions/graphql/queries/ledger/closingBookStructures.d.ts +6 -0
  50. package/extensions/graphql/queries/ledger/closingBookStructures.js +27 -0
  51. package/extensions/graphql/queries/ledger/closingBookStructures.ts +25 -0
  52. package/extensions/graphql/queries/ledger/elements.d.ts +6 -0
  53. package/extensions/graphql/queries/ledger/elements.js +56 -0
  54. package/extensions/graphql/queries/ledger/elements.ts +54 -0
  55. package/extensions/graphql/queries/ledger/entities.d.ts +7 -0
  56. package/extensions/graphql/queries/ledger/entities.js +31 -0
  57. package/extensions/graphql/queries/ledger/entities.ts +29 -0
  58. package/extensions/graphql/queries/ledger/entity.d.ts +14 -0
  59. package/extensions/graphql/queries/ledger/entity.js +55 -0
  60. package/extensions/graphql/queries/ledger/entity.ts +54 -0
  61. package/extensions/graphql/queries/ledger/fiscalCalendar.d.ts +8 -0
  62. package/extensions/graphql/queries/ledger/fiscalCalendar.js +35 -0
  63. package/extensions/graphql/queries/ledger/fiscalCalendar.ts +33 -0
  64. package/extensions/graphql/queries/ledger/mappedTrialBalance.d.ts +9 -0
  65. package/extensions/graphql/queries/ledger/mappedTrialBalance.js +29 -0
  66. package/extensions/graphql/queries/ledger/mappedTrialBalance.ts +27 -0
  67. package/extensions/graphql/queries/ledger/mapping.d.ts +5 -0
  68. package/extensions/graphql/queries/ledger/mapping.js +35 -0
  69. package/extensions/graphql/queries/ledger/mapping.ts +33 -0
  70. package/extensions/graphql/queries/ledger/mappingCoverage.d.ts +5 -0
  71. package/extensions/graphql/queries/ledger/mappingCoverage.js +22 -0
  72. package/extensions/graphql/queries/ledger/mappingCoverage.ts +20 -0
  73. package/extensions/graphql/queries/ledger/mappings.d.ts +6 -0
  74. package/extensions/graphql/queries/ledger/mappings.js +23 -0
  75. package/extensions/graphql/queries/ledger/mappings.ts +21 -0
  76. package/extensions/graphql/queries/ledger/periodCloseStatus.d.ts +6 -0
  77. package/extensions/graphql/queries/ledger/periodCloseStatus.js +29 -0
  78. package/extensions/graphql/queries/ledger/periodCloseStatus.ts +27 -0
  79. package/extensions/graphql/queries/ledger/periodDrafts.d.ts +8 -0
  80. package/extensions/graphql/queries/ledger/periodDrafts.js +45 -0
  81. package/extensions/graphql/queries/ledger/periodDrafts.ts +43 -0
  82. package/extensions/graphql/queries/ledger/publishList.d.ts +5 -0
  83. package/extensions/graphql/queries/ledger/publishList.js +29 -0
  84. package/extensions/graphql/queries/ledger/publishList.ts +27 -0
  85. package/extensions/graphql/queries/ledger/publishLists.d.ts +5 -0
  86. package/extensions/graphql/queries/ledger/publishLists.js +29 -0
  87. package/extensions/graphql/queries/ledger/publishLists.ts +27 -0
  88. package/extensions/graphql/queries/ledger/report.d.ts +5 -0
  89. package/extensions/graphql/queries/ledger/report.js +40 -0
  90. package/extensions/graphql/queries/ledger/report.ts +38 -0
  91. package/extensions/graphql/queries/ledger/reportingTaxonomy.d.ts +6 -0
  92. package/extensions/graphql/queries/ledger/reportingTaxonomy.js +27 -0
  93. package/extensions/graphql/queries/ledger/reportingTaxonomy.ts +25 -0
  94. package/extensions/graphql/queries/ledger/reports.d.ts +8 -0
  95. package/extensions/graphql/queries/ledger/reports.js +45 -0
  96. package/extensions/graphql/queries/ledger/reports.ts +43 -0
  97. package/extensions/graphql/queries/ledger/scheduleFacts.d.ts +7 -0
  98. package/extensions/graphql/queries/ledger/scheduleFacts.js +24 -0
  99. package/extensions/graphql/queries/ledger/scheduleFacts.ts +22 -0
  100. package/extensions/graphql/queries/ledger/schedules.d.ts +6 -0
  101. package/extensions/graphql/queries/ledger/schedules.js +24 -0
  102. package/extensions/graphql/queries/ledger/schedules.ts +22 -0
  103. package/extensions/graphql/queries/ledger/statement.d.ts +9 -0
  104. package/extensions/graphql/queries/ledger/statement.js +43 -0
  105. package/extensions/graphql/queries/ledger/statement.ts +41 -0
  106. package/extensions/graphql/queries/ledger/structures.d.ts +5 -0
  107. package/extensions/graphql/queries/ledger/structures.js +22 -0
  108. package/extensions/graphql/queries/ledger/structures.ts +20 -0
  109. package/extensions/graphql/queries/ledger/summary.d.ts +7 -0
  110. package/extensions/graphql/queries/ledger/summary.js +25 -0
  111. package/extensions/graphql/queries/ledger/summary.ts +23 -0
  112. package/extensions/graphql/queries/ledger/taxonomies.d.ts +5 -0
  113. package/extensions/graphql/queries/ledger/taxonomies.js +28 -0
  114. package/extensions/graphql/queries/ledger/taxonomies.ts +26 -0
  115. package/extensions/graphql/queries/ledger/transaction.d.ts +6 -0
  116. package/extensions/graphql/queries/ledger/transaction.js +49 -0
  117. package/extensions/graphql/queries/ledger/transaction.ts +47 -0
  118. package/extensions/graphql/queries/ledger/transactions.d.ts +7 -0
  119. package/extensions/graphql/queries/ledger/transactions.js +49 -0
  120. package/extensions/graphql/queries/ledger/transactions.ts +47 -0
  121. package/extensions/graphql/queries/ledger/trialBalance.d.ts +7 -0
  122. package/extensions/graphql/queries/ledger/trialBalance.js +28 -0
  123. package/extensions/graphql/queries/ledger/trialBalance.ts +26 -0
  124. package/extensions/graphql/queries/ledger/unmappedElements.d.ts +8 -0
  125. package/extensions/graphql/queries/ledger/unmappedElements.js +29 -0
  126. package/extensions/graphql/queries/ledger/unmappedElements.ts +27 -0
  127. package/extensions/hooks.d.ts +1 -1
  128. package/extensions/index.d.ts +18 -1
  129. package/extensions/index.js +26 -1
  130. package/extensions/index.ts +49 -2
  131. package/index.ts +2 -2
  132. package/package.json +14 -5
  133. package/sdk/index.d.ts +2 -2
  134. package/sdk/index.js +38 -73
  135. package/sdk/index.ts +2 -2
  136. package/sdk/sdk.gen.d.ts +100 -325
  137. package/sdk/sdk.gen.js +228 -592
  138. package/sdk/sdk.gen.ts +193 -556
  139. package/sdk/types.gen.d.ts +4522 -7708
  140. package/sdk/types.gen.ts +4569 -7983
  141. package/sdk.gen.d.ts +100 -325
  142. package/sdk.gen.js +228 -592
  143. package/sdk.gen.ts +193 -556
  144. package/types.gen.d.ts +4522 -7708
  145. package/types.gen.ts +4569 -7983
  146. package/extensions/AgentClient.test.ts +0 -403
  147. package/extensions/LedgerClient.test.ts +0 -1655
  148. package/extensions/OperationClient.test.ts +0 -365
  149. package/extensions/QueryClient.test.ts +0 -432
  150. package/extensions/ReportClient.test.ts +0 -828
  151. package/extensions/SSEClient.test.ts +0 -375
  152. package/extensions/config.test.ts +0 -199
  153. package/extensions/hooks.test.ts +0 -373
  154. package/extensions/index.test.ts +0 -420
  155. package/sdk-extensions/AgentClient.d.ts +0 -82
  156. package/sdk-extensions/AgentClient.js +0 -218
  157. package/sdk-extensions/AgentClient.test.ts +0 -403
  158. package/sdk-extensions/AgentClient.ts +0 -321
  159. package/sdk-extensions/LedgerClient.d.ts +0 -416
  160. package/sdk-extensions/LedgerClient.js +0 -737
  161. package/sdk-extensions/LedgerClient.test.ts +0 -1655
  162. package/sdk-extensions/LedgerClient.ts +0 -1249
  163. package/sdk-extensions/OperationClient.d.ts +0 -65
  164. package/sdk-extensions/OperationClient.js +0 -251
  165. package/sdk-extensions/OperationClient.test.ts +0 -365
  166. package/sdk-extensions/OperationClient.ts +0 -324
  167. package/sdk-extensions/QueryClient.d.ts +0 -52
  168. package/sdk-extensions/QueryClient.js +0 -313
  169. package/sdk-extensions/QueryClient.test.ts +0 -432
  170. package/sdk-extensions/QueryClient.ts +0 -420
  171. package/sdk-extensions/README.md +0 -901
  172. package/sdk-extensions/ReportClient.d.ts +0 -151
  173. package/sdk-extensions/ReportClient.js +0 -278
  174. package/sdk-extensions/ReportClient.test.ts +0 -828
  175. package/sdk-extensions/ReportClient.ts +0 -486
  176. package/sdk-extensions/SSEClient.d.ts +0 -62
  177. package/sdk-extensions/SSEClient.js +0 -154
  178. package/sdk-extensions/SSEClient.test.ts +0 -375
  179. package/sdk-extensions/SSEClient.ts +0 -210
  180. package/sdk-extensions/config.d.ts +0 -57
  181. package/sdk-extensions/config.js +0 -152
  182. package/sdk-extensions/config.test.ts +0 -199
  183. package/sdk-extensions/config.ts +0 -175
  184. package/sdk-extensions/hooks.d.ts +0 -110
  185. package/sdk-extensions/hooks.js +0 -384
  186. package/sdk-extensions/hooks.test.ts +0 -373
  187. package/sdk-extensions/hooks.ts +0 -459
  188. package/sdk-extensions/index.d.ts +0 -63
  189. package/sdk-extensions/index.js +0 -164
  190. package/sdk-extensions/index.test.ts +0 -420
  191. package/sdk-extensions/index.ts +0 -203
@@ -3,174 +3,260 @@
3
3
  /**
4
4
  * Ledger Client for RoboSystems API
5
5
  *
6
- * High-level client for all ledger concerns: chart of accounts, transactions,
7
- * trial balance, taxonomy, mappings, and AI auto-mapping. This is the
8
- * operational backbone reports consume these as inputs.
6
+ * High-level facade for everything the RoboLedger domain exposes:
7
+ * entity, chart of accounts, transactions, taxonomy + mappings, fiscal
8
+ * calendar, schedules, reports, and publish lists.
9
+ *
10
+ * **Transport split:**
11
+ * - **Reads** go through GraphQL at `/extensions/{graph_id}/graphql`
12
+ * (via `graphql-request`, with typed documents produced by GraphQL
13
+ * Code Generator). The graph is in the URL, not in the query.
14
+ * - **Writes** go through named command operations at
15
+ * `/extensions/roboledger/{graph_id}/operations/{operation_name}`
16
+ * (via the OpenAPI-generated `opXxx` functions in `../sdk/sdk.gen`).
17
+ * Each command returns an `OperationEnvelope`; the facade unwraps
18
+ * `envelope.result` and returns a friendly camelCase type.
19
+ *
20
+ * Consumers don't need to know which transport a method uses — the
21
+ * facade signature stays stable. The only trick is that write method
22
+ * results are cast from the envelope's untyped `result` field.
9
23
  */
10
24
 
25
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
26
+ import { ClientError } from 'graphql-request'
11
27
  import {
12
- autoMapElements,
13
- closeFiscalPeriod,
14
- createClosingEntry,
15
- createManualClosingEntry,
16
- createMappingAssociation,
17
- createSchedule,
18
- createStructure,
19
- deleteMappingAssociation,
20
- getAccountRollups,
21
- getClosingBookStructures,
22
- getFiscalCalendar,
23
- getLedgerAccountTree,
24
- getLedgerEntity,
25
- getLedgerSummary,
26
- getLedgerTransaction,
27
- getLedgerTrialBalance,
28
- getMappedTrialBalance,
29
- getMappingCoverage,
30
- getMappingDetail,
31
- getPeriodCloseStatus,
32
- getReportingTaxonomy,
33
- getScheduleFacts,
34
- initializeLedger,
35
- listElements,
36
- listLedgerAccounts,
37
- listLedgerTransactions,
38
- listMappings,
39
- listPeriodDrafts,
40
- listSchedules,
41
- listStructures,
42
- reopenFiscalPeriod,
43
- setCloseTarget,
44
- truncateSchedule,
28
+ opAutoMapElements,
29
+ opClosePeriod,
30
+ opCreateClosingEntry,
31
+ opCreateManualClosingEntry,
32
+ opCreateMappingAssociation,
33
+ opCreateSchedule,
34
+ opCreateStructure,
35
+ opCreateTaxonomy,
36
+ opDeleteMappingAssociation,
37
+ opInitializeLedger,
38
+ opReopenPeriod,
39
+ opSetCloseTarget,
40
+ opTruncateSchedule,
41
+ opUpdateEntity,
45
42
  } from '../sdk.gen'
46
43
  import type {
47
- AccountListResponse,
48
- AccountRollupsResponse,
49
- AccountTreeResponse,
50
- ClosePeriodRequest,
51
- ClosePeriodResponse,
52
- ClosingBookStructuresResponse,
53
- ClosingEntryResponse,
54
- CreateClosingEntryRequest,
44
+ AutoMapElementsOperation,
45
+ ClosePeriodOperation,
46
+ CreateClosingEntryOperation,
55
47
  CreateManualClosingEntryRequest,
48
+ CreateMappingAssociationOperation,
56
49
  CreateScheduleRequest,
57
- FiscalCalendarResponse,
50
+ CreateStructureRequest,
51
+ CreateTaxonomyRequest,
52
+ DeleteMappingAssociationOperation,
58
53
  InitializeLedgerRequest,
59
- InitializeLedgerResponse,
60
- LedgerSummaryResponse,
61
- LedgerTransactionDetailResponse,
62
- LedgerTransactionListResponse,
63
- MappingCoverageResponse,
64
- MappingDetailResponse,
65
- PeriodCloseItemResponse,
66
- PeriodCloseStatusResponse,
67
- PeriodDraftsResponse,
68
- ReopenPeriodRequest,
69
- ScheduleCreatedResponse,
70
- ScheduleFactResponse,
71
- ScheduleFactsResponse,
72
- ScheduleListResponse,
73
- ScheduleSummaryResponse,
74
- SetCloseTargetRequest,
75
- TrialBalanceResponse,
76
- TruncateScheduleRequest,
77
- TruncateScheduleResponse,
54
+ OperationEnvelope,
55
+ ReopenPeriodOperation,
56
+ SetCloseTargetOperation,
57
+ TruncateScheduleOperation,
58
+ UpdateEntityRequest,
78
59
  } from '../types.gen'
79
-
80
- // ── Friendly types ──────────────────────────────────────────────────────
81
-
82
- export interface LedgerEntity {
83
- id: string
84
- name: string
85
- legalName: string | null
86
- entityType: string | null
87
- industry: string | null
88
- status: string | null
60
+ import type { TokenProvider } from './graphql/client'
61
+ import { GraphQLClientCache } from './graphql/client'
62
+ import {
63
+ GetLedgerAccountRollupsDocument,
64
+ GetLedgerAccountTreeDocument,
65
+ GetLedgerClosingBookStructuresDocument,
66
+ GetLedgerEntityDocument,
67
+ GetLedgerFiscalCalendarDocument,
68
+ GetLedgerMappedTrialBalanceDocument,
69
+ GetLedgerMappingCoverageDocument,
70
+ GetLedgerMappingDocument,
71
+ GetLedgerPeriodCloseStatusDocument,
72
+ GetLedgerPeriodDraftsDocument,
73
+ GetLedgerReportingTaxonomyDocument,
74
+ GetLedgerScheduleFactsDocument,
75
+ GetLedgerSummaryDocument,
76
+ GetLedgerTransactionDocument,
77
+ GetLedgerTrialBalanceDocument,
78
+ ListLedgerAccountsDocument,
79
+ ListLedgerElementsDocument,
80
+ ListLedgerEntitiesDocument,
81
+ ListLedgerMappingsDocument,
82
+ ListLedgerSchedulesDocument,
83
+ ListLedgerStructuresDocument,
84
+ ListLedgerTaxonomiesDocument,
85
+ ListLedgerTransactionsDocument,
86
+ ListLedgerUnmappedElementsDocument,
87
+ type GetLedgerAccountRollupsQuery,
88
+ type GetLedgerAccountTreeQuery,
89
+ type GetLedgerClosingBookStructuresQuery,
90
+ type GetLedgerEntityQuery,
91
+ type GetLedgerFiscalCalendarQuery,
92
+ type GetLedgerMappedTrialBalanceQuery,
93
+ type GetLedgerMappingCoverageQuery,
94
+ type GetLedgerMappingQuery,
95
+ type GetLedgerPeriodCloseStatusQuery,
96
+ type GetLedgerPeriodDraftsQuery,
97
+ type GetLedgerReportingTaxonomyQuery,
98
+ type GetLedgerScheduleFactsQuery,
99
+ type GetLedgerSummaryQuery,
100
+ type GetLedgerTransactionQuery,
101
+ type GetLedgerTrialBalanceQuery,
102
+ type ListLedgerAccountsQuery,
103
+ type ListLedgerElementsQuery,
104
+ type ListLedgerEntitiesQuery,
105
+ type ListLedgerMappingsQuery,
106
+ type ListLedgerSchedulesQuery,
107
+ type ListLedgerStructuresQuery,
108
+ type ListLedgerTaxonomiesQuery,
109
+ type ListLedgerTransactionsQuery,
110
+ type ListLedgerUnmappedElementsQuery,
111
+ } from './graphql/generated/graphql'
112
+
113
+ // ── Friendly types derived from GraphQL codegen ────────────────────────
114
+ //
115
+ // These are the single source of truth for read payload shapes. Write
116
+ // methods also return these where the operation result is semantically
117
+ // the same thing (e.g. close-period returns the updated fiscal calendar,
118
+ // the same shape as the fiscalCalendar read).
119
+
120
+ export type LedgerEntity = NonNullable<GetLedgerEntityQuery['entity']>
121
+ export type LedgerEntitySummary = ListLedgerEntitiesQuery['entities'][number]
122
+
123
+ export type LedgerSummary = NonNullable<GetLedgerSummaryQuery['summary']>
124
+
125
+ export type LedgerAccountList = NonNullable<ListLedgerAccountsQuery['accounts']>
126
+ export type LedgerAccount = LedgerAccountList['accounts'][number]
127
+ export type LedgerAccountTree = NonNullable<GetLedgerAccountTreeQuery['accountTree']>
128
+ export type LedgerAccountRollups = NonNullable<GetLedgerAccountRollupsQuery['accountRollups']>
129
+
130
+ export type LedgerTrialBalance = NonNullable<GetLedgerTrialBalanceQuery['trialBalance']>
131
+ export type LedgerMappedTrialBalance = NonNullable<
132
+ GetLedgerMappedTrialBalanceQuery['mappedTrialBalance']
133
+ >
134
+
135
+ export type LedgerTransactionList = NonNullable<ListLedgerTransactionsQuery['transactions']>
136
+ export type LedgerTransactionListItem = LedgerTransactionList['transactions'][number]
137
+ export type LedgerTransaction = NonNullable<GetLedgerTransactionQuery['transaction']>
138
+
139
+ export type LedgerReportingTaxonomy = NonNullable<
140
+ GetLedgerReportingTaxonomyQuery['reportingTaxonomy']
141
+ >
142
+ export type LedgerTaxonomyList = NonNullable<ListLedgerTaxonomiesQuery['taxonomies']>
143
+ export type LedgerTaxonomy = LedgerTaxonomyList['taxonomies'][number]
144
+
145
+ export type LedgerElementList = NonNullable<ListLedgerElementsQuery['elements']>
146
+ export type LedgerElement = LedgerElementList['elements'][number]
147
+ export type LedgerUnmappedElement = ListLedgerUnmappedElementsQuery['unmappedElements'][number]
148
+
149
+ export type LedgerStructureList = NonNullable<ListLedgerStructuresQuery['structures']>
150
+ export type LedgerStructure = LedgerStructureList['structures'][number]
151
+
152
+ export type LedgerMappingList = NonNullable<ListLedgerMappingsQuery['mappings']>
153
+ export type LedgerMappingInfo = LedgerMappingList['structures'][number]
154
+ export type LedgerMapping = NonNullable<GetLedgerMappingQuery['mapping']>
155
+ export type LedgerMappingCoverage = NonNullable<GetLedgerMappingCoverageQuery['mappingCoverage']>
156
+
157
+ export type LedgerScheduleList = NonNullable<ListLedgerSchedulesQuery['schedules']>
158
+ export type LedgerSchedule = LedgerScheduleList['schedules'][number]
159
+ export type LedgerScheduleFacts = NonNullable<GetLedgerScheduleFactsQuery['scheduleFacts']>
160
+ export type LedgerScheduleFact = LedgerScheduleFacts['facts'][number]
161
+
162
+ export type LedgerPeriodCloseStatus = NonNullable<
163
+ GetLedgerPeriodCloseStatusQuery['periodCloseStatus']
164
+ >
165
+ export type LedgerPeriodCloseItem = LedgerPeriodCloseStatus['schedules'][number]
166
+ export type LedgerPeriodDrafts = NonNullable<GetLedgerPeriodDraftsQuery['periodDrafts']>
167
+ export type LedgerDraftEntry = LedgerPeriodDrafts['drafts'][number]
168
+ export type LedgerDraftLineItem = LedgerDraftEntry['lineItems'][number]
169
+
170
+ export type LedgerClosingBookStructures = NonNullable<
171
+ GetLedgerClosingBookStructuresQuery['closingBookStructures']
172
+ >
173
+
174
+ export type LedgerFiscalCalendar = NonNullable<GetLedgerFiscalCalendarQuery['fiscalCalendar']>
175
+ export type LedgerFiscalPeriod = LedgerFiscalCalendar['periods'][number]
176
+
177
+ // ── Write result shapes (envelope.result payloads) ─────────────────────
178
+ //
179
+ // Backend Pydantic models serialize these write results in snake_case.
180
+ // The facade converts to camelCase where the result is meaningful to
181
+ // consumers; for simple ack/ack-with-id payloads we keep the raw shape.
182
+
183
+ /** Snake-case shape returned in envelope.result for fiscal calendar writes. */
184
+ interface RawFiscalCalendar {
185
+ graph_id: string
186
+ fiscal_year_start_month: number
187
+ closed_through: string | null
188
+ close_target: string | null
189
+ gap_periods: number
190
+ catch_up_sequence: string[]
191
+ closeable_now: boolean
192
+ blockers: string[]
193
+ last_close_at: string | null
194
+ initialized_at: string | null
195
+ last_sync_at: string | null
196
+ periods: Array<{
197
+ name: string
198
+ start_date: string
199
+ end_date: string
200
+ status: string
201
+ closed_at: string | null
202
+ }>
89
203
  }
90
204
 
91
- export interface MappingInfo {
92
- id: string
93
- name: string
94
- description: string | null
95
- structureType: string
96
- taxonomyId: string
97
- isActive: boolean
205
+ interface RawInitializeLedgerResult {
206
+ fiscal_calendar: RawFiscalCalendar
207
+ periods_created: number
208
+ warnings: string[]
98
209
  }
99
210
 
100
- export interface MappingCoverage {
101
- totalCoaElements: number
102
- mappedCount: number
103
- unmappedCount: number
104
- coveragePercent: number
105
- highConfidence: number
106
- mediumConfidence: number
107
- lowConfidence: number
211
+ interface RawClosePeriodResult {
212
+ period: string
213
+ entries_posted: number
214
+ target_auto_advanced: boolean
215
+ fiscal_calendar: RawFiscalCalendar
108
216
  }
109
217
 
110
- export interface Structure {
111
- id: string
112
- name: string
113
- structureType: string
218
+ interface RawClosingEntryResult {
219
+ outcome: ClosingEntryOutcome
220
+ entry_id: string | null
221
+ status: string | null
222
+ posting_date: string | null
223
+ memo: string | null
224
+ debit_element_id: string | null
225
+ credit_element_id: string | null
226
+ amount: number | null
227
+ reason: string | null
114
228
  }
115
229
 
116
- export interface Schedule {
117
- structureId: string
230
+ interface RawScheduleCreatedResult {
231
+ structure_id: string
118
232
  name: string
119
- taxonomyName: string
120
- entryTemplate: Record<string, unknown> | null
121
- scheduleMetadata: Record<string, unknown> | null
122
- totalPeriods: number
123
- periodsWithEntries: number
233
+ taxonomy_id: string
234
+ total_periods: number
235
+ total_facts: number
124
236
  }
125
237
 
126
- export interface ScheduleCreated {
127
- structureId: string
128
- name: string
129
- taxonomyId: string
130
- totalPeriods: number
131
- totalFacts: number
132
- }
133
-
134
- export interface ScheduleFact {
135
- elementId: string
136
- elementName: string
137
- value: number
138
- periodStart: string
139
- periodEnd: string
238
+ interface RawTruncateScheduleResult {
239
+ structure_id: string
240
+ new_end_date: string
241
+ facts_deleted: number
242
+ reason: string
140
243
  }
141
244
 
142
- export interface PeriodCloseItem {
143
- structureId: string
144
- structureName: string
145
- amount: number
146
- status: string
147
- entryId: string | null
245
+ export interface InitializeLedgerResult {
246
+ fiscalCalendar: LedgerFiscalCalendar
247
+ periodsCreated: number
248
+ warnings: string[]
148
249
  }
149
250
 
150
- export interface PeriodCloseStatus {
151
- fiscalPeriodStart: string
152
- fiscalPeriodEnd: string
153
- periodStatus: string
154
- schedules: PeriodCloseItem[]
155
- totalDraft: number
156
- totalPosted: number
251
+ export interface ClosePeriodResult {
252
+ period: string
253
+ entriesPosted: number
254
+ targetAutoAdvanced: boolean
255
+ fiscalCalendar: LedgerFiscalCalendar
157
256
  }
158
257
 
159
- /**
160
- * Outcome of an idempotent `createClosingEntry` call.
161
- *
162
- * - `created` — no prior draft, new draft created
163
- * - `unchanged` — prior draft matches current schedule fact, no-op
164
- * - `regenerated` — prior draft was stale, replaced with a fresh one
165
- * - `removed` — prior draft existed but schedule no longer covers this period
166
- * - `skipped` — no prior draft and no in-scope fact; nothing to do
167
- */
168
258
  export type ClosingEntryOutcome = 'created' | 'unchanged' | 'regenerated' | 'removed' | 'skipped'
169
259
 
170
- /**
171
- * Result of an idempotent closing-entry call. `entry_id`, `amount`, and
172
- * related fields are null for `removed` and `skipped` outcomes.
173
- */
174
260
  export interface ClosingEntry {
175
261
  outcome: ClosingEntryOutcome
176
262
  entryId: string | null
@@ -183,33 +269,25 @@ export interface ClosingEntry {
183
269
  reason: string | null
184
270
  }
185
271
 
186
- export type LedgerEntryType = 'standard' | 'adjusting' | 'closing' | 'reversing'
187
-
188
- // ── Fiscal calendar types ──────────────────────────────────────────────
189
-
190
- export interface FiscalPeriodSummary {
272
+ export interface ScheduleCreated {
273
+ structureId: string
191
274
  name: string
192
- startDate: string
193
- endDate: string
194
- status: string
195
- closedAt: string | null
275
+ taxonomyId: string
276
+ totalPeriods: number
277
+ totalFacts: number
196
278
  }
197
279
 
198
- export interface FiscalCalendarState {
199
- graphId: string
200
- fiscalYearStartMonth: number
201
- closedThrough: string | null
202
- closeTarget: string | null
203
- gapPeriods: number
204
- catchUpSequence: string[]
205
- closeableNow: boolean
206
- blockers: string[]
207
- lastCloseAt: string | null
208
- initializedAt: string | null
209
- lastSyncAt: string | null
210
- periods: FiscalPeriodSummary[]
280
+ export interface TruncateScheduleResult {
281
+ structureId: string
282
+ newEndDate: string
283
+ factsDeleted: number
284
+ reason: string
211
285
  }
212
286
 
287
+ export type LedgerEntryType = 'standard' | 'adjusting' | 'closing' | 'reversing'
288
+
289
+ // ── Caller-facing option interfaces ────────────────────────────────────
290
+
213
291
  export interface InitializeLedgerOptions {
214
292
  closedThrough?: string | null
215
293
  fiscalYearStartMonth?: number
@@ -218,59 +296,11 @@ export interface InitializeLedgerOptions {
218
296
  note?: string | null
219
297
  }
220
298
 
221
- export interface InitializeLedgerResult {
222
- fiscalCalendar: FiscalCalendarState
223
- periodsCreated: number
224
- warnings: string[]
225
- }
226
-
227
299
  export interface ClosePeriodOptions {
228
300
  note?: string | null
229
301
  allowStaleSync?: boolean
230
302
  }
231
303
 
232
- export interface ClosePeriodResult {
233
- period: string
234
- entriesPosted: number
235
- targetAutoAdvanced: boolean
236
- fiscalCalendar: FiscalCalendarState
237
- }
238
-
239
- export interface DraftLineItemView {
240
- lineItemId: string
241
- elementId: string
242
- elementCode: string | null
243
- elementName: string
244
- debitAmount: number
245
- creditAmount: number
246
- description: string | null
247
- }
248
-
249
- export interface DraftEntryView {
250
- entryId: string
251
- postingDate: string
252
- type: string
253
- memo: string | null
254
- provenance: string | null
255
- sourceStructureId: string | null
256
- sourceStructureName: string | null
257
- lineItems: DraftLineItemView[]
258
- totalDebit: number
259
- totalCredit: number
260
- balanced: boolean
261
- }
262
-
263
- export interface PeriodDraftsView {
264
- period: string
265
- periodStart: string
266
- periodEnd: string
267
- draftCount: number
268
- totalDebit: number
269
- totalCredit: number
270
- allBalanced: boolean
271
- drafts: DraftEntryView[]
272
- }
273
-
274
304
  export interface ManualClosingLineItem {
275
305
  elementId: string
276
306
  debitAmount?: number
@@ -290,13 +320,6 @@ export interface TruncateScheduleOptions {
290
320
  reason: string
291
321
  }
292
322
 
293
- export interface TruncateScheduleResult {
294
- structureId: string
295
- newEndDate: string
296
- factsDeleted: number
297
- reason: string
298
- }
299
-
300
323
  export interface CreateScheduleOptions {
301
324
  name: string
302
325
  elementIds: string[]
@@ -321,230 +344,265 @@ export interface CreateScheduleOptions {
321
344
 
322
345
  // ── Client ──────────────────────────────────────────────────────────────
323
346
 
347
+ interface LedgerClientConfig {
348
+ baseUrl: string
349
+ credentials?: 'include' | 'same-origin' | 'omit'
350
+ headers?: Record<string, string>
351
+ /** Static credential — use `tokenProvider` instead if the JWT rotates. */
352
+ token?: string
353
+ /**
354
+ * Dynamic credential callback. When set, invoked on every GraphQL
355
+ * request so refreshes flow through automatically.
356
+ */
357
+ tokenProvider?: TokenProvider
358
+ }
359
+
324
360
  export class LedgerClient {
325
- private config: {
326
- baseUrl: string
327
- credentials?: 'include' | 'same-origin' | 'omit'
328
- headers?: Record<string, string>
329
- token?: string
330
- }
361
+ private config: LedgerClientConfig
362
+
363
+ /**
364
+ * Per-graph GraphQL client cache. The first call for a given graph
365
+ * creates a `GraphQLClient` bound to `/extensions/{graph_id}/graphql`;
366
+ * subsequent calls reuse it.
367
+ */
368
+ private gql: GraphQLClientCache
331
369
 
332
- constructor(config: {
333
- baseUrl: string
334
- credentials?: 'include' | 'same-origin' | 'omit'
335
- headers?: Record<string, string>
336
- token?: string
337
- }) {
370
+ constructor(config: LedgerClientConfig) {
338
371
  this.config = config
372
+ this.gql = new GraphQLClientCache(config)
339
373
  }
340
374
 
341
375
  // ── Entity ──────────────────────────────────────────────────────────
342
376
 
343
377
  /**
344
378
  * Get the entity (company/organization) for this graph.
379
+ * Returns null when the ledger has no entity yet.
345
380
  */
346
381
  async getEntity(graphId: string): Promise<LedgerEntity | null> {
347
- const response = await getLedgerEntity({
348
- path: { graph_id: graphId },
349
- })
350
-
351
- if (response.response.status === 404) return null
352
- if (response.error) {
353
- throw new Error(`Get entity failed: ${JSON.stringify(response.error)}`)
354
- }
355
-
356
- const data = response.data as Record<string, unknown>
357
- return {
358
- id: data.id as string,
359
- name: data.name as string,
360
- legalName: (data.legal_name as string) ?? null,
361
- entityType: (data.entity_type as string) ?? null,
362
- industry: (data.industry as string) ?? null,
363
- status: (data.status as string) ?? null,
364
- }
382
+ return this.gqlQuery(
383
+ graphId,
384
+ GetLedgerEntityDocument,
385
+ undefined,
386
+ 'Get entity',
387
+ (data) => data.entity
388
+ )
365
389
  }
366
390
 
367
- // ── Accounts (Chart of Accounts) ───────────────────────────────────
391
+ /** List all entities for this graph, optionally filtered by source system. */
392
+ async listEntities(
393
+ graphId: string,
394
+ options?: { source?: string }
395
+ ): Promise<LedgerEntitySummary[]> {
396
+ return this.gqlQuery(
397
+ graphId,
398
+ ListLedgerEntitiesDocument,
399
+ { source: options?.source ?? null },
400
+ 'List entities',
401
+ (data) => data.entities
402
+ )
403
+ }
368
404
 
369
405
  /**
370
- * List accounts (flat).
406
+ * Update the entity for this graph. Only non-null fields are applied.
407
+ * Returns the updated entity.
371
408
  */
372
- async listAccounts(graphId: string): Promise<AccountListResponse> {
373
- const response = await listLedgerAccounts({
374
- path: { graph_id: graphId },
375
- })
409
+ async updateEntity(graphId: string, updates: UpdateEntityRequest): Promise<LedgerEntity> {
410
+ const envelope = await this.callOperation(
411
+ 'Update entity',
412
+ opUpdateEntity({
413
+ path: { graph_id: graphId },
414
+ body: updates,
415
+ })
416
+ )
417
+ return envelope.result as unknown as LedgerEntity
418
+ }
376
419
 
377
- if (response.error) {
378
- throw new Error(`List accounts failed: ${JSON.stringify(response.error)}`)
379
- }
420
+ // ── Summary ────────────────────────────────────────────────────────
380
421
 
381
- return response.data as AccountListResponse
422
+ /** Ledger rollup counts + QB sync metadata. */
423
+ async getSummary(graphId: string): Promise<LedgerSummary | null> {
424
+ return this.gqlQuery(
425
+ graphId,
426
+ GetLedgerSummaryDocument,
427
+ undefined,
428
+ 'Get summary',
429
+ (data) => data.summary
430
+ )
382
431
  }
383
432
 
384
- /**
385
- * Get the account tree (hierarchical).
386
- */
387
- async getAccountTree(graphId: string): Promise<AccountTreeResponse> {
388
- const response = await getLedgerAccountTree({
389
- path: { graph_id: graphId },
390
- })
433
+ // ── Accounts (Chart of Accounts) ───────────────────────────────────
391
434
 
392
- if (response.error) {
393
- throw new Error(`Get account tree failed: ${JSON.stringify(response.error)}`)
435
+ /** List CoA accounts with optional filters and pagination. */
436
+ async listAccounts(
437
+ graphId: string,
438
+ options?: {
439
+ classification?: string
440
+ isActive?: boolean
441
+ limit?: number
442
+ offset?: number
394
443
  }
395
-
396
- return response.data as AccountTreeResponse
444
+ ): Promise<LedgerAccountList | null> {
445
+ return this.gqlQuery(
446
+ graphId,
447
+ ListLedgerAccountsDocument,
448
+ {
449
+ classification: options?.classification ?? null,
450
+ isActive: options?.isActive ?? null,
451
+ limit: options?.limit ?? 100,
452
+ offset: options?.offset ?? 0,
453
+ },
454
+ 'List accounts',
455
+ (data) => data.accounts
456
+ )
397
457
  }
398
458
 
399
- // ── Transactions ────────────────────────────────────────────────────
459
+ /** Hierarchical Chart of Accounts (up to 4 levels deep). */
460
+ async getAccountTree(graphId: string): Promise<LedgerAccountTree | null> {
461
+ return this.gqlQuery(
462
+ graphId,
463
+ GetLedgerAccountTreeDocument,
464
+ undefined,
465
+ 'Get account tree',
466
+ (data) => data.accountTree
467
+ )
468
+ }
400
469
 
401
- /**
402
- * List transactions with optional date filters.
403
- */
404
- async listTransactions(
470
+ /** Accounts rolled up to reporting concepts via a mapping structure. */
471
+ async getAccountRollups(
405
472
  graphId: string,
406
- options?: { startDate?: string; endDate?: string; limit?: number; offset?: number }
407
- ): Promise<LedgerTransactionListResponse> {
408
- const response = await listLedgerTransactions({
409
- path: { graph_id: graphId },
410
- query: {
411
- start_date: options?.startDate,
412
- end_date: options?.endDate,
413
- limit: options?.limit,
414
- offset: options?.offset,
473
+ options?: { mappingId?: string; startDate?: string; endDate?: string }
474
+ ): Promise<LedgerAccountRollups | null> {
475
+ return this.gqlQuery(
476
+ graphId,
477
+ GetLedgerAccountRollupsDocument,
478
+ {
479
+ mappingId: options?.mappingId ?? null,
480
+ startDate: options?.startDate ?? null,
481
+ endDate: options?.endDate ?? null,
415
482
  },
416
- })
417
-
418
- if (response.error) {
419
- throw new Error(`List transactions failed: ${JSON.stringify(response.error)}`)
420
- }
421
-
422
- return response.data as LedgerTransactionListResponse
483
+ 'Get account rollups',
484
+ (data) => data.accountRollups
485
+ )
423
486
  }
424
487
 
425
- /**
426
- * Get transaction detail with entries and line items.
427
- */
428
- async getTransaction(
488
+ // ── Transactions ────────────────────────────────────────────────────
489
+
490
+ /** List transactions with optional type + date filters and pagination. */
491
+ async listTransactions(
429
492
  graphId: string,
430
- transactionId: string
431
- ): Promise<LedgerTransactionDetailResponse> {
432
- const response = await getLedgerTransaction({
433
- path: { graph_id: graphId, transaction_id: transactionId },
434
- })
435
-
436
- if (response.error) {
437
- throw new Error(`Get transaction failed: ${JSON.stringify(response.error)}`)
493
+ options?: {
494
+ type?: string
495
+ startDate?: string
496
+ endDate?: string
497
+ limit?: number
498
+ offset?: number
438
499
  }
500
+ ): Promise<LedgerTransactionList | null> {
501
+ return this.gqlQuery(
502
+ graphId,
503
+ ListLedgerTransactionsDocument,
504
+ {
505
+ type: options?.type ?? null,
506
+ startDate: options?.startDate ?? null,
507
+ endDate: options?.endDate ?? null,
508
+ limit: options?.limit ?? 100,
509
+ offset: options?.offset ?? 0,
510
+ },
511
+ 'List transactions',
512
+ (data) => data.transactions
513
+ )
514
+ }
439
515
 
440
- return response.data as LedgerTransactionDetailResponse
516
+ /** Get transaction detail with entries + line items. */
517
+ async getTransaction(graphId: string, transactionId: string): Promise<LedgerTransaction | null> {
518
+ return this.gqlQuery(
519
+ graphId,
520
+ GetLedgerTransactionDocument,
521
+ { transactionId },
522
+ 'Get transaction',
523
+ (data) => data.transaction
524
+ )
441
525
  }
442
526
 
443
527
  // ── Trial Balance ──────────────────────────────────────────────────
444
528
 
445
- /**
446
- * Get trial balance (CoA-level debits/credits).
447
- */
529
+ /** Trial balance by raw CoA account. */
448
530
  async getTrialBalance(
449
531
  graphId: string,
450
532
  options?: { startDate?: string; endDate?: string }
451
- ): Promise<TrialBalanceResponse> {
452
- const response = await getLedgerTrialBalance({
453
- path: { graph_id: graphId },
454
- query: {
455
- start_date: options?.startDate,
456
- end_date: options?.endDate,
533
+ ): Promise<LedgerTrialBalance | null> {
534
+ return this.gqlQuery(
535
+ graphId,
536
+ GetLedgerTrialBalanceDocument,
537
+ {
538
+ startDate: options?.startDate ?? null,
539
+ endDate: options?.endDate ?? null,
457
540
  },
458
- })
459
-
460
- if (response.error) {
461
- throw new Error(`Get trial balance failed: ${JSON.stringify(response.error)}`)
462
- }
463
-
464
- return response.data as TrialBalanceResponse
541
+ 'Get trial balance',
542
+ (data) => data.trialBalance
543
+ )
465
544
  }
466
545
 
467
- /**
468
- * Get mapped trial balance (CoA rolled up to GAAP concepts).
469
- */
546
+ /** Trial balance rolled up to GAAP reporting concepts via a mapping. */
470
547
  async getMappedTrialBalance(
471
548
  graphId: string,
472
- options?: { mappingId?: string; startDate?: string; endDate?: string }
473
- ): Promise<unknown> {
474
- const response = await getMappedTrialBalance({
475
- path: { graph_id: graphId },
476
- query: {
477
- mapping_id: options?.mappingId,
478
- start_date: options?.startDate,
479
- end_date: options?.endDate,
549
+ mappingId: string,
550
+ options?: { startDate?: string; endDate?: string }
551
+ ): Promise<LedgerMappedTrialBalance | null> {
552
+ return this.gqlQuery(
553
+ graphId,
554
+ GetLedgerMappedTrialBalanceDocument,
555
+ {
556
+ mappingId,
557
+ startDate: options?.startDate ?? null,
558
+ endDate: options?.endDate ?? null,
480
559
  },
481
- })
482
-
483
- if (response.error) {
484
- throw new Error(`Get mapped trial balance failed: ${JSON.stringify(response.error)}`)
485
- }
486
-
487
- return response.data
488
- }
489
-
490
- // ── Summary ────────────────────────────────────────────────────────
491
-
492
- /**
493
- * Get ledger summary (account count, transaction count, date range).
494
- */
495
- async getSummary(graphId: string): Promise<LedgerSummaryResponse> {
496
- const response = await getLedgerSummary({
497
- path: { graph_id: graphId },
498
- })
499
-
500
- if (response.error) {
501
- throw new Error(`Get summary failed: ${JSON.stringify(response.error)}`)
502
- }
503
-
504
- return response.data as LedgerSummaryResponse
560
+ 'Get mapped trial balance',
561
+ (data) => data.mappedTrialBalance
562
+ )
505
563
  }
506
564
 
507
565
  // ── Taxonomy ────────────────────────────────────────────────────────
508
566
 
509
- /**
510
- * Get the reporting taxonomy (US GAAP seed).
511
- */
512
- async getReportingTaxonomy(graphId: string): Promise<unknown> {
513
- const response = await getReportingTaxonomy({
514
- path: { graph_id: graphId },
515
- })
516
-
517
- if (response.error) {
518
- throw new Error(`Get reporting taxonomy failed: ${JSON.stringify(response.error)}`)
519
- }
520
-
521
- return response.data
567
+ /** Get the locked US GAAP reporting taxonomy for this graph. */
568
+ async getReportingTaxonomy(graphId: string): Promise<LedgerReportingTaxonomy | null> {
569
+ return this.gqlQuery(
570
+ graphId,
571
+ GetLedgerReportingTaxonomyDocument,
572
+ undefined,
573
+ 'Get reporting taxonomy',
574
+ (data) => data.reportingTaxonomy
575
+ )
522
576
  }
523
577
 
524
- /**
525
- * List reporting structures (IS, BS, CF) for a taxonomy.
526
- */
527
- async listStructures(graphId: string, taxonomyId?: string): Promise<Structure[]> {
528
- const response = await listStructures({
529
- path: { graph_id: graphId },
530
- query: taxonomyId ? { taxonomy_id: taxonomyId } : undefined,
531
- })
532
-
533
- if (response.error) {
534
- throw new Error(`List structures failed: ${JSON.stringify(response.error)}`)
535
- }
578
+ /** List active taxonomies with optional type filter. */
579
+ async listTaxonomies(
580
+ graphId: string,
581
+ options?: { taxonomyType?: string }
582
+ ): Promise<LedgerTaxonomy[]> {
583
+ const list = await this.gqlQuery(
584
+ graphId,
585
+ ListLedgerTaxonomiesDocument,
586
+ { taxonomyType: options?.taxonomyType ?? null },
587
+ 'List taxonomies',
588
+ (data) => data.taxonomies
589
+ )
590
+ return list?.taxonomies ?? []
591
+ }
536
592
 
537
- const data = response.data as { structures?: Array<Record<string, unknown>> }
538
- return (data.structures ?? []).map((s) => ({
539
- id: s.id as string,
540
- name: s.name as string,
541
- structureType: s.structure_type as string,
542
- }))
593
+ /** Create a new taxonomy (used for CoA + mapping taxonomies). */
594
+ async createTaxonomy(
595
+ graphId: string,
596
+ body: CreateTaxonomyRequest
597
+ ): Promise<Record<string, unknown>> {
598
+ const envelope = await this.callOperation(
599
+ 'Create taxonomy',
600
+ opCreateTaxonomy({ path: { graph_id: graphId }, body })
601
+ )
602
+ return (envelope.result ?? {}) as Record<string, unknown>
543
603
  }
544
604
 
545
- /**
546
- * List elements (CoA accounts, GAAP concepts, etc.).
547
- */
605
+ /** List elements (CoA accounts, GAAP concepts, etc.) with filters. */
548
606
  async listElements(
549
607
  graphId: string,
550
608
  options?: {
@@ -555,184 +613,183 @@ export class LedgerClient {
555
613
  limit?: number
556
614
  offset?: number
557
615
  }
558
- ): Promise<unknown> {
559
- const response = await listElements({
560
- path: { graph_id: graphId },
561
- query: {
562
- taxonomy_id: options?.taxonomyId,
563
- source: options?.source,
564
- classification: options?.classification,
565
- is_abstract: options?.isAbstract,
566
- limit: options?.limit,
567
- offset: options?.offset,
616
+ ): Promise<LedgerElementList | null> {
617
+ return this.gqlQuery(
618
+ graphId,
619
+ ListLedgerElementsDocument,
620
+ {
621
+ taxonomyId: options?.taxonomyId ?? null,
622
+ source: options?.source ?? null,
623
+ classification: options?.classification ?? null,
624
+ isAbstract: options?.isAbstract ?? null,
625
+ limit: options?.limit ?? 100,
626
+ offset: options?.offset ?? 0,
568
627
  },
569
- })
570
-
571
- if (response.error) {
572
- throw new Error(`List elements failed: ${JSON.stringify(response.error)}`)
573
- }
628
+ 'List elements',
629
+ (data) => data.elements
630
+ )
631
+ }
574
632
 
575
- return response.data
633
+ /** CoA elements not yet mapped to a reporting concept. */
634
+ async listUnmappedElements(
635
+ graphId: string,
636
+ options?: { mappingId?: string }
637
+ ): Promise<LedgerUnmappedElement[]> {
638
+ return this.gqlQuery(
639
+ graphId,
640
+ ListLedgerUnmappedElementsDocument,
641
+ { mappingId: options?.mappingId ?? null },
642
+ 'List unmapped elements',
643
+ (data) => data.unmappedElements
644
+ )
576
645
  }
577
646
 
578
- // ── Mappings ────────────────────────────────────────────────────────
647
+ // ── Structures ──────────────────────────────────────────────────────
579
648
 
580
- /**
581
- * Create a new CoA→GAAP mapping structure.
582
- * Returns the created mapping info.
583
- */
584
- async createMappingStructure(
649
+ /** List reporting structures (IS, BS, CF, schedules) with optional filters. */
650
+ async listStructures(
585
651
  graphId: string,
586
- options?: { name?: string; description?: string; taxonomyId?: string }
587
- ): Promise<MappingInfo> {
588
- const response = await createStructure({
589
- path: { graph_id: graphId },
590
- body: {
591
- name: options?.name ?? 'CoA to Reporting',
592
- description: options?.description ?? 'Map Chart of Accounts to US GAAP reporting concepts',
593
- structure_type: 'coa_mapping',
594
- taxonomy_id: options?.taxonomyId ?? 'tax_usgaap_reporting',
652
+ options?: { taxonomyId?: string; structureType?: string }
653
+ ): Promise<LedgerStructure[]> {
654
+ const list = await this.gqlQuery(
655
+ graphId,
656
+ ListLedgerStructuresDocument,
657
+ {
658
+ taxonomyId: options?.taxonomyId ?? null,
659
+ structureType: options?.structureType ?? null,
595
660
  },
596
- })
597
-
598
- if (response.error) {
599
- throw new Error(`Create mapping structure failed: ${JSON.stringify(response.error)}`)
600
- }
601
-
602
- const data = response.data as Record<string, unknown>
603
- return {
604
- id: data.id as string,
605
- name: data.name as string,
606
- description: (data.description as string) ?? null,
607
- structureType: data.structure_type as string,
608
- taxonomyId: data.taxonomy_id as string,
609
- isActive: (data.is_active as boolean) ?? true,
610
- }
661
+ 'List structures',
662
+ (data) => data.structures
663
+ )
664
+ return list?.structures ?? []
611
665
  }
612
666
 
613
667
  /**
614
- * List available CoA→GAAP mapping structures.
668
+ * Create a new structure. Most common use is a CoA→reporting mapping
669
+ * structure; also used for custom statement + schedule structures.
615
670
  */
616
- async listMappings(graphId: string): Promise<MappingInfo[]> {
617
- const response = await listMappings({
618
- path: { graph_id: graphId },
619
- })
620
-
621
- if (response.error) {
622
- throw new Error(`List mappings failed: ${JSON.stringify(response.error)}`)
623
- }
624
-
625
- const data = response.data as { structures?: Array<Record<string, unknown>> }
626
- return (data.structures ?? []).map((s) => ({
627
- id: s.id as string,
628
- name: s.name as string,
629
- description: (s.description as string) ?? null,
630
- structureType: s.structure_type as string,
631
- taxonomyId: s.taxonomy_id as string,
632
- isActive: (s.is_active as boolean) ?? true,
633
- }))
671
+ async createStructure(graphId: string, body: CreateStructureRequest): Promise<LedgerMappingInfo> {
672
+ const envelope = await this.callOperation(
673
+ 'Create structure',
674
+ opCreateStructure({ path: { graph_id: graphId }, body })
675
+ )
676
+ return envelope.result as unknown as LedgerMappingInfo
634
677
  }
635
678
 
636
- /**
637
- * Get mapping detail — all associations with element names.
638
- */
639
- async getMappingDetail(graphId: string, mappingId: string): Promise<MappingDetailResponse> {
640
- const response = await getMappingDetail({
641
- path: { graph_id: graphId, mapping_id: mappingId },
642
- })
643
-
644
- if (response.error) {
645
- throw new Error(`Get mapping detail failed: ${JSON.stringify(response.error)}`)
646
- }
679
+ // ── Mappings ────────────────────────────────────────────────────────
647
680
 
648
- return response.data as MappingDetailResponse
681
+ /** List active CoA→reporting mapping structures. */
682
+ async listMappings(graphId: string): Promise<LedgerMappingInfo[]> {
683
+ const list = await this.gqlQuery(
684
+ graphId,
685
+ ListLedgerMappingsDocument,
686
+ undefined,
687
+ 'List mappings',
688
+ (data) => data.mappings
689
+ )
690
+ return list?.structures ?? []
649
691
  }
650
692
 
651
- /**
652
- * Get mapping coverage how many CoA elements are mapped.
653
- */
654
- async getMappingCoverage(graphId: string, mappingId: string): Promise<MappingCoverage> {
655
- const response = await getMappingCoverage({
656
- path: { graph_id: graphId, mapping_id: mappingId },
657
- })
658
-
659
- if (response.error) {
660
- throw new Error(`Get mapping coverage failed: ${JSON.stringify(response.error)}`)
661
- }
662
-
663
- const data = response.data as MappingCoverageResponse
664
- return {
665
- totalCoaElements: data.total_coa_elements ?? 0,
666
- mappedCount: data.mapped_count ?? 0,
667
- unmappedCount: data.unmapped_count ?? 0,
668
- coveragePercent: data.coverage_percent ?? 0,
669
- highConfidence: data.high_confidence ?? 0,
670
- mediumConfidence: data.medium_confidence ?? 0,
671
- lowConfidence: data.low_confidence ?? 0,
672
- }
693
+ /** Get a mapping structure with all its associations. */
694
+ async getMapping(graphId: string, mappingId: string): Promise<LedgerMapping | null> {
695
+ return this.gqlQuery(
696
+ graphId,
697
+ GetLedgerMappingDocument,
698
+ { mappingId },
699
+ 'Get mapping',
700
+ (data) => data.mapping
701
+ )
673
702
  }
674
703
 
675
- /**
676
- * Create a manual mapping association (CoA element → GAAP element).
677
- */
678
- async createMapping(
704
+ /** Mapping coverage stats — how many CoA elements are mapped. */
705
+ async getMappingCoverage(
679
706
  graphId: string,
680
- mappingId: string,
681
- fromElementId: string,
682
- toElementId: string,
683
- confidence?: number
684
- ): Promise<void> {
685
- const response = await createMappingAssociation({
686
- path: { graph_id: graphId, mapping_id: mappingId },
687
- body: {
688
- from_element_id: fromElementId,
689
- to_element_id: toElementId,
690
- confidence: confidence ?? 1.0,
691
- },
692
- })
693
-
694
- if (response.error) {
695
- throw new Error(`Create mapping failed: ${JSON.stringify(response.error)}`)
696
- }
707
+ mappingId: string
708
+ ): Promise<LedgerMappingCoverage | null> {
709
+ return this.gqlQuery(
710
+ graphId,
711
+ GetLedgerMappingCoverageDocument,
712
+ { mappingId },
713
+ 'Get mapping coverage',
714
+ (data) => data.mappingCoverage
715
+ )
697
716
  }
698
717
 
699
- /**
700
- * Delete a mapping association.
701
- */
702
- async deleteMapping(graphId: string, mappingId: string, associationId: string): Promise<void> {
703
- const response = await deleteMappingAssociation({
704
- path: { graph_id: graphId, mapping_id: mappingId, association_id: associationId },
705
- })
718
+ /** Create a manual mapping association between two elements. */
719
+ async createMappingAssociation(
720
+ graphId: string,
721
+ body: CreateMappingAssociationOperation
722
+ ): Promise<Record<string, unknown>> {
723
+ const envelope = await this.callOperation(
724
+ 'Create mapping association',
725
+ opCreateMappingAssociation({ path: { graph_id: graphId }, body })
726
+ )
727
+ return (envelope.result ?? {}) as Record<string, unknown>
728
+ }
706
729
 
707
- if (response.error) {
708
- throw new Error(`Delete mapping failed: ${JSON.stringify(response.error)}`)
709
- }
730
+ /** Delete a mapping association. */
731
+ async deleteMappingAssociation(
732
+ graphId: string,
733
+ body: DeleteMappingAssociationOperation
734
+ ): Promise<{ deleted: boolean }> {
735
+ const envelope = await this.callOperation(
736
+ 'Delete mapping association',
737
+ opDeleteMappingAssociation({ path: { graph_id: graphId }, body })
738
+ )
739
+ return (envelope.result ?? { deleted: true }) as { deleted: boolean }
710
740
  }
711
741
 
712
742
  /**
713
- * Trigger AI auto-mapping (MappingAgent).
714
- * Returns immediately — the agent runs in the background.
743
+ * Trigger the AI MappingAgent. Returns the operation id — async; the
744
+ * agent runs in the background. Consumers can subscribe to progress
745
+ * via `/v1/operations/{operationId}/stream`.
715
746
  */
716
- async autoMap(graphId: string, mappingId: string): Promise<{ operationId?: string }> {
717
- const response = await autoMapElements({
718
- path: { graph_id: graphId, mapping_id: mappingId },
719
- })
747
+ async autoMapElements(
748
+ graphId: string,
749
+ body: AutoMapElementsOperation
750
+ ): Promise<{ operationId: string; status: OperationEnvelope['status'] }> {
751
+ const envelope = await this.callOperation(
752
+ 'Auto-map elements',
753
+ opAutoMapElements({ path: { graph_id: graphId }, body })
754
+ )
755
+ return { operationId: envelope.operationId, status: envelope.status }
756
+ }
720
757
 
721
- if (response.error) {
722
- throw new Error(`Auto-map failed: ${JSON.stringify(response.error)}`)
723
- }
758
+ // ── Schedules ──────────────────────────────────────────────────────
724
759
 
725
- const data = response.data as Record<string, unknown> | undefined
726
- return {
727
- operationId: data?.operation_id as string | undefined,
728
- }
760
+ /** List all schedule structures with metadata. */
761
+ async listSchedules(graphId: string): Promise<LedgerSchedule[]> {
762
+ const list = await this.gqlQuery(
763
+ graphId,
764
+ ListLedgerSchedulesDocument,
765
+ undefined,
766
+ 'List schedules',
767
+ (data) => data.schedules
768
+ )
769
+ return list?.schedules ?? []
729
770
  }
730
771
 
731
- // ── Schedules ──────────────────────────────────────────────────────
772
+ /** Schedule facts optionally filtered by period window. */
773
+ async getScheduleFacts(
774
+ graphId: string,
775
+ structureId: string,
776
+ options?: { periodStart?: string; periodEnd?: string }
777
+ ): Promise<LedgerScheduleFact[]> {
778
+ const facts = await this.gqlQuery(
779
+ graphId,
780
+ GetLedgerScheduleFactsDocument,
781
+ {
782
+ structureId,
783
+ periodStart: options?.periodStart ?? null,
784
+ periodEnd: options?.periodEnd ?? null,
785
+ },
786
+ 'Get schedule facts',
787
+ (data) => data.scheduleFacts
788
+ )
789
+ return facts?.facts ?? []
790
+ }
732
791
 
733
- /**
734
- * Create a schedule with pre-generated monthly facts.
735
- */
792
+ /** Create a new schedule with pre-generated monthly facts. */
736
793
  async createSchedule(graphId: string, options: CreateScheduleOptions): Promise<ScheduleCreated> {
737
794
  const body: CreateScheduleRequest = {
738
795
  name: options.name,
@@ -757,124 +814,75 @@ export class LedgerClient {
757
814
  }
758
815
  : undefined,
759
816
  }
760
-
761
- const response = await createSchedule({
762
- path: { graph_id: graphId },
763
- body,
764
- })
765
-
766
- if (response.error) {
767
- throw new Error(`Create schedule failed: ${JSON.stringify(response.error)}`)
768
- }
769
-
770
- const data = response.data as ScheduleCreatedResponse
817
+ const envelope = await this.callOperation(
818
+ 'Create schedule',
819
+ opCreateSchedule({ path: { graph_id: graphId }, body })
820
+ )
821
+ const raw = envelope.result as unknown as RawScheduleCreatedResult
771
822
  return {
772
- structureId: data.structure_id,
773
- name: data.name,
774
- taxonomyId: data.taxonomy_id,
775
- totalPeriods: data.total_periods,
776
- totalFacts: data.total_facts,
777
- }
778
- }
779
-
780
- /**
781
- * List all schedules for a graph.
782
- */
783
- async listSchedules(graphId: string): Promise<Schedule[]> {
784
- const response = await listSchedules({
785
- path: { graph_id: graphId },
786
- })
787
-
788
- if (response.error) {
789
- throw new Error(`List schedules failed: ${JSON.stringify(response.error)}`)
823
+ structureId: raw.structure_id,
824
+ name: raw.name,
825
+ taxonomyId: raw.taxonomy_id,
826
+ totalPeriods: raw.total_periods,
827
+ totalFacts: raw.total_facts,
790
828
  }
791
-
792
- const data = response.data as ScheduleListResponse
793
- return (data.schedules ?? []).map((s: ScheduleSummaryResponse) => ({
794
- structureId: s.structure_id,
795
- name: s.name,
796
- taxonomyName: s.taxonomy_name,
797
- entryTemplate: s.entry_template ?? null,
798
- scheduleMetadata: s.schedule_metadata ?? null,
799
- totalPeriods: s.total_periods,
800
- periodsWithEntries: s.periods_with_entries,
801
- }))
802
829
  }
803
830
 
804
- /**
805
- * Get facts for a schedule, optionally filtered by period.
806
- */
807
- async getScheduleFacts(
831
+ /** Truncate a schedule — end it early at `newEndDate`. */
832
+ async truncateSchedule(
808
833
  graphId: string,
809
834
  structureId: string,
810
- periodStart?: string,
811
- periodEnd?: string
812
- ): Promise<ScheduleFact[]> {
813
- const response = await getScheduleFacts({
814
- path: { graph_id: graphId, structure_id: structureId },
815
- query: {
816
- period_start: periodStart ?? null,
817
- period_end: periodEnd ?? null,
818
- },
819
- })
820
-
821
- if (response.error) {
822
- throw new Error(`Get schedule facts failed: ${JSON.stringify(response.error)}`)
835
+ options: TruncateScheduleOptions
836
+ ): Promise<TruncateScheduleResult> {
837
+ const body: TruncateScheduleOperation = {
838
+ structure_id: structureId,
839
+ new_end_date: options.newEndDate,
840
+ reason: options.reason,
841
+ }
842
+ const envelope = await this.callOperation(
843
+ 'Truncate schedule',
844
+ opTruncateSchedule({ path: { graph_id: graphId }, body })
845
+ )
846
+ const raw = envelope.result as unknown as RawTruncateScheduleResult
847
+ return {
848
+ structureId: raw.structure_id,
849
+ newEndDate: raw.new_end_date,
850
+ factsDeleted: raw.facts_deleted,
851
+ reason: raw.reason,
823
852
  }
824
-
825
- const data = response.data as ScheduleFactsResponse
826
- return (data.facts ?? []).map((f: ScheduleFactResponse) => ({
827
- elementId: f.element_id,
828
- elementName: f.element_name,
829
- value: f.value,
830
- periodStart: f.period_start,
831
- periodEnd: f.period_end,
832
- }))
833
853
  }
834
854
 
835
- /**
836
- * Get close status for all schedules in a fiscal period.
837
- */
855
+ // ── Period close ────────────────────────────────────────────────────
856
+
857
+ /** Close status for all schedules in a fiscal period. */
838
858
  async getPeriodCloseStatus(
839
859
  graphId: string,
840
860
  periodStart: string,
841
861
  periodEnd: string
842
- ): Promise<PeriodCloseStatus> {
843
- const response = await getPeriodCloseStatus({
844
- path: { graph_id: graphId },
845
- query: { period_start: periodStart, period_end: periodEnd },
846
- })
847
-
848
- if (response.error) {
849
- throw new Error(`Get period close status failed: ${JSON.stringify(response.error)}`)
850
- }
862
+ ): Promise<LedgerPeriodCloseStatus | null> {
863
+ return this.gqlQuery(
864
+ graphId,
865
+ GetLedgerPeriodCloseStatusDocument,
866
+ { periodStart, periodEnd },
867
+ 'Get period close status',
868
+ (data) => data.periodCloseStatus
869
+ )
870
+ }
851
871
 
852
- const data = response.data as PeriodCloseStatusResponse
853
- return {
854
- fiscalPeriodStart: data.fiscal_period_start,
855
- fiscalPeriodEnd: data.fiscal_period_end,
856
- periodStatus: data.period_status,
857
- schedules: (data.schedules ?? []).map((s: PeriodCloseItemResponse) => ({
858
- structureId: s.structure_id,
859
- structureName: s.structure_name,
860
- amount: s.amount,
861
- status: s.status,
862
- entryId: s.entry_id ?? null,
863
- })),
864
- totalDraft: data.total_draft,
865
- totalPosted: data.total_posted,
866
- }
872
+ /** All draft entries in a period, fully expanded for review pre-close. */
873
+ async listPeriodDrafts(graphId: string, period: string): Promise<LedgerPeriodDrafts | null> {
874
+ return this.gqlQuery(
875
+ graphId,
876
+ GetLedgerPeriodDraftsDocument,
877
+ { period },
878
+ 'List period drafts',
879
+ (data) => data.periodDrafts
880
+ )
867
881
  }
868
882
 
869
883
  /**
870
- * Idempotently create (or refresh) a draft closing entry from a schedule's
871
- * facts for a period. The `outcome` field describes what actually happened:
872
- *
873
- * - `created` — new draft
874
- * - `unchanged` — existing draft still matches the schedule; no-op
875
- * - `regenerated` — existing draft was stale; replaced
876
- * - `removed` — schedule no longer covers this period; stale draft deleted
877
- * - `skipped` — no existing draft and no in-scope fact; nothing to do
884
+ * Idempotently create (or refresh) a draft closing entry from a
885
+ * schedule for a period. See `ClosingEntryOutcome` for semantics.
878
886
  */
879
887
  async createClosingEntry(
880
888
  graphId: string,
@@ -884,76 +892,73 @@ export class LedgerClient {
884
892
  periodEnd: string,
885
893
  memo?: string
886
894
  ): Promise<ClosingEntry> {
887
- const body: CreateClosingEntryRequest = {
895
+ const body: CreateClosingEntryOperation = {
896
+ structure_id: structureId,
888
897
  posting_date: postingDate,
889
898
  period_start: periodStart,
890
899
  period_end: periodEnd,
891
900
  memo: memo ?? undefined,
892
901
  }
893
-
894
- const response = await createClosingEntry({
895
- path: { graph_id: graphId, structure_id: structureId },
896
- body,
897
- })
898
-
899
- if (response.error) {
900
- throw new Error(`Create closing entry failed: ${JSON.stringify(response.error)}`)
901
- }
902
-
903
- const data = response.data as ClosingEntryResponse
904
- return toClosingEntry(data)
902
+ const envelope = await this.callOperation(
903
+ 'Create closing entry',
904
+ opCreateClosingEntry({ path: { graph_id: graphId }, body })
905
+ )
906
+ return rawToClosingEntry(envelope.result as unknown as RawClosingEntryResult)
905
907
  }
906
908
 
907
- // ── Closing Book ─────────────────────────────────────────────────────
908
-
909
909
  /**
910
- * Get all closing book structure categories for the sidebar.
911
- * Returns statements, account rollups, schedules, trial balance,
912
- * and period close grouped into categories.
910
+ * Create a manual balanced closing entry (not tied to a schedule).
911
+ * Used for disposals, adjustments, and one-off closing events.
913
912
  */
914
- async getClosingBookStructures(graphId: string): Promise<ClosingBookStructuresResponse> {
915
- const response = await getClosingBookStructures({
916
- path: { graph_id: graphId },
917
- })
918
-
919
- if (response.error) {
920
- throw new Error(`Get closing book structures failed: ${JSON.stringify(response.error)}`)
921
- }
922
-
923
- return response.data as ClosingBookStructuresResponse
924
- }
925
-
926
- /**
927
- * Get account rollups — CoA accounts grouped by reporting element with balances.
928
- * Shows how company-specific accounts roll up to standardized reporting lines.
929
- */
930
- async getAccountRollups(
913
+ async createManualClosingEntry(
931
914
  graphId: string,
932
- options?: { mappingId?: string; startDate?: string; endDate?: string }
933
- ): Promise<AccountRollupsResponse> {
934
- const response = await getAccountRollups({
935
- path: { graph_id: graphId },
936
- query: {
937
- mapping_id: options?.mappingId,
938
- start_date: options?.startDate,
939
- end_date: options?.endDate,
940
- },
941
- })
942
-
943
- if (response.error) {
944
- throw new Error(`Get account rollups failed: ${JSON.stringify(response.error)}`)
915
+ options: CreateManualClosingEntryOptions
916
+ ): Promise<ClosingEntry> {
917
+ const body: CreateManualClosingEntryRequest = {
918
+ posting_date: options.postingDate,
919
+ memo: options.memo,
920
+ entry_type: options.entryType,
921
+ line_items: options.lineItems.map((li) => ({
922
+ element_id: li.elementId,
923
+ debit_amount: li.debitAmount ?? 0,
924
+ credit_amount: li.creditAmount ?? 0,
925
+ description: li.description ?? null,
926
+ })),
945
927
  }
928
+ const envelope = await this.callOperation(
929
+ 'Create manual closing entry',
930
+ opCreateManualClosingEntry({ path: { graph_id: graphId }, body })
931
+ )
932
+ return rawToClosingEntry(envelope.result as unknown as RawClosingEntryResult)
933
+ }
946
934
 
947
- return response.data as AccountRollupsResponse
935
+ // ── Closing book ───────────────────────────────────────────────────
936
+
937
+ /** Grouped closing book structures for the close-screen sidebar. */
938
+ async getClosingBookStructures(graphId: string): Promise<LedgerClosingBookStructures | null> {
939
+ return this.gqlQuery(
940
+ graphId,
941
+ GetLedgerClosingBookStructuresDocument,
942
+ undefined,
943
+ 'Get closing book structures',
944
+ (data) => data.closingBookStructures
945
+ )
948
946
  }
949
947
 
950
- // ── Fiscal Calendar ─────────────────────────────────────────────────
948
+ // ── Fiscal Calendar ────────────────────────────────────────────────
949
+
950
+ /** Current fiscal calendar state — pointers, gap, closeable status. */
951
+ async getFiscalCalendar(graphId: string): Promise<LedgerFiscalCalendar | null> {
952
+ return this.gqlQuery(
953
+ graphId,
954
+ GetLedgerFiscalCalendarDocument,
955
+ undefined,
956
+ 'Get fiscal calendar',
957
+ (data) => data.fiscalCalendar
958
+ )
959
+ }
951
960
 
952
- /**
953
- * Initialize the fiscal calendar for a graph. Creates FiscalPeriod rows
954
- * for the data window, sets `closed_through` / `close_target`, and emits
955
- * an `initialized` audit event. Fails with 409 if already initialized.
956
- */
961
+ /** One-time ledger initialization — seed fiscal calendar + periods. */
957
962
  async initializeLedger(
958
963
  graphId: string,
959
964
  options?: InitializeLedgerOptions
@@ -965,255 +970,131 @@ export class LedgerClient {
965
970
  auto_seed_schedules: options?.autoSeedSchedules,
966
971
  note: options?.note ?? null,
967
972
  }
968
-
969
- const response = await initializeLedger({
970
- path: { graph_id: graphId },
971
- body,
972
- })
973
-
974
- if (response.error) {
975
- throw new Error(`Initialize ledger failed: ${JSON.stringify(response.error)}`)
976
- }
977
-
978
- const data = response.data as InitializeLedgerResponse
973
+ const envelope = await this.callOperation(
974
+ 'Initialize ledger',
975
+ opInitializeLedger({ path: { graph_id: graphId }, body })
976
+ )
977
+ const raw = envelope.result as unknown as RawInitializeLedgerResult
979
978
  return {
980
- fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
981
- periodsCreated: data.periods_created ?? 0,
982
- warnings: data.warnings ?? [],
979
+ fiscalCalendar: rawFiscalCalendarToCamel(raw.fiscal_calendar),
980
+ periodsCreated: raw.periods_created ?? 0,
981
+ warnings: raw.warnings ?? [],
983
982
  }
984
983
  }
985
984
 
986
- /**
987
- * Get the current fiscal calendar state — pointers, gap, closeable status.
988
- */
989
- async getFiscalCalendar(graphId: string): Promise<FiscalCalendarState> {
990
- const response = await getFiscalCalendar({
991
- path: { graph_id: graphId },
992
- })
993
-
994
- if (response.error) {
995
- throw new Error(`Get fiscal calendar failed: ${JSON.stringify(response.error)}`)
996
- }
997
-
998
- return toFiscalCalendarState(response.data as FiscalCalendarResponse)
999
- }
1000
-
1001
- /**
1002
- * Set the close target for a graph. Validates that the target is not in
1003
- * the future and not before `closed_through`.
1004
- */
985
+ /** Set the user-controlled close target (YYYY-MM). */
1005
986
  async setCloseTarget(
1006
987
  graphId: string,
1007
988
  period: string,
1008
989
  note?: string | null
1009
- ): Promise<FiscalCalendarState> {
1010
- const body: SetCloseTargetRequest = {
1011
- period,
1012
- note: note ?? null,
1013
- }
1014
-
1015
- const response = await setCloseTarget({
1016
- path: { graph_id: graphId },
1017
- body,
1018
- })
1019
-
1020
- if (response.error) {
1021
- throw new Error(`Set close target failed: ${JSON.stringify(response.error)}`)
1022
- }
1023
-
1024
- return toFiscalCalendarState(response.data as FiscalCalendarResponse)
990
+ ): Promise<LedgerFiscalCalendar> {
991
+ const body: SetCloseTargetOperation = { period, note: note ?? null }
992
+ const envelope = await this.callOperation(
993
+ 'Set close target',
994
+ opSetCloseTarget({ path: { graph_id: graphId }, body })
995
+ )
996
+ return rawFiscalCalendarToCamel(envelope.result as unknown as RawFiscalCalendar)
1025
997
  }
1026
998
 
1027
- /**
1028
- * Close a fiscal period — the final commit action.
1029
- *
1030
- * Validates closeable gates, transitions all draft entries in the period
1031
- * to `posted`, marks the FiscalPeriod closed, and advances `closed_through`
1032
- * (auto-advancing `close_target` when reached).
1033
- */
999
+ /** Close a fiscal period — the final commit action. */
1034
1000
  async closePeriod(
1035
1001
  graphId: string,
1036
1002
  period: string,
1037
1003
  options?: ClosePeriodOptions
1038
1004
  ): Promise<ClosePeriodResult> {
1039
- const body: ClosePeriodRequest = {
1005
+ const body: ClosePeriodOperation = {
1006
+ period,
1040
1007
  note: options?.note ?? null,
1041
1008
  allow_stale_sync: options?.allowStaleSync,
1042
1009
  }
1043
-
1044
- const response = await closeFiscalPeriod({
1045
- path: { graph_id: graphId, period },
1046
- body,
1047
- })
1048
-
1049
- if (response.error) {
1050
- throw new Error(`Close period failed: ${JSON.stringify(response.error)}`)
1051
- }
1052
-
1053
- const data = response.data as ClosePeriodResponse
1010
+ const envelope = await this.callOperation(
1011
+ 'Close period',
1012
+ opClosePeriod({ path: { graph_id: graphId }, body })
1013
+ )
1014
+ const raw = envelope.result as unknown as RawClosePeriodResult
1054
1015
  return {
1055
- period: data.period,
1056
- entriesPosted: data.entries_posted ?? 0,
1057
- targetAutoAdvanced: data.target_auto_advanced ?? false,
1058
- fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
1016
+ period: raw.period,
1017
+ entriesPosted: raw.entries_posted ?? 0,
1018
+ targetAutoAdvanced: raw.target_auto_advanced ?? false,
1019
+ fiscalCalendar: rawFiscalCalendarToCamel(raw.fiscal_calendar),
1059
1020
  }
1060
1021
  }
1061
1022
 
1062
- /**
1063
- * Reopen a closed fiscal period. Requires a non-empty `reason` for the
1064
- * audit log. Posted entries stay posted; the period transitions to
1065
- * `closing` so the user can post adjustments and re-close.
1066
- */
1023
+ /** Reopen a closed fiscal period. Requires a reason for the audit log. */
1067
1024
  async reopenPeriod(
1068
1025
  graphId: string,
1069
1026
  period: string,
1070
1027
  reason: string,
1071
1028
  note?: string | null
1072
- ): Promise<FiscalCalendarState> {
1073
- const body: ReopenPeriodRequest = {
1029
+ ): Promise<LedgerFiscalCalendar> {
1030
+ const body: ReopenPeriodOperation = {
1031
+ period,
1074
1032
  reason,
1075
1033
  note: note ?? null,
1076
1034
  }
1077
-
1078
- const response = await reopenFiscalPeriod({
1079
- path: { graph_id: graphId, period },
1080
- body,
1081
- })
1082
-
1083
- if (response.error) {
1084
- throw new Error(`Reopen period failed: ${JSON.stringify(response.error)}`)
1085
- }
1086
-
1087
- return toFiscalCalendarState(response.data as FiscalCalendarResponse)
1035
+ const envelope = await this.callOperation(
1036
+ 'Reopen period',
1037
+ opReopenPeriod({ path: { graph_id: graphId }, body })
1038
+ )
1039
+ return rawFiscalCalendarToCamel(envelope.result as unknown as RawFiscalCalendar)
1088
1040
  }
1089
1041
 
1090
- /**
1091
- * List all draft entries in a fiscal period for review before close.
1092
- * Fully expanded with line items, element metadata, and per-entry balance.
1093
- *
1094
- * Pure read — call repeatedly without side effects.
1095
- */
1096
- async listPeriodDrafts(graphId: string, period: string): Promise<PeriodDraftsView> {
1097
- const response = await listPeriodDrafts({
1098
- path: { graph_id: graphId, period },
1099
- })
1042
+ // Reports, statements, and publish lists live on `ReportClient`.
1100
1043
 
1101
- if (response.error) {
1102
- throw new Error(`List period drafts failed: ${JSON.stringify(response.error)}`)
1103
- }
1104
-
1105
- const data = response.data as PeriodDraftsResponse
1106
- return {
1107
- period: data.period,
1108
- periodStart: data.period_start,
1109
- periodEnd: data.period_end,
1110
- draftCount: data.draft_count,
1111
- totalDebit: data.total_debit,
1112
- totalCredit: data.total_credit,
1113
- allBalanced: data.all_balanced,
1114
- drafts: (data.drafts ?? []).map((d) => ({
1115
- entryId: d.entry_id,
1116
- postingDate: d.posting_date,
1117
- type: d.type,
1118
- memo: d.memo ?? null,
1119
- provenance: d.provenance ?? null,
1120
- sourceStructureId: d.source_structure_id ?? null,
1121
- sourceStructureName: d.source_structure_name ?? null,
1122
- lineItems: d.line_items.map((li) => ({
1123
- lineItemId: li.line_item_id,
1124
- elementId: li.element_id,
1125
- elementCode: li.element_code ?? null,
1126
- elementName: li.element_name,
1127
- debitAmount: li.debit_amount,
1128
- creditAmount: li.credit_amount,
1129
- description: li.description ?? null,
1130
- })),
1131
- totalDebit: d.total_debit,
1132
- totalCredit: d.total_credit,
1133
- balanced: d.balanced,
1134
- })),
1135
- }
1136
- }
1137
-
1138
- // ── Schedule mutations ──────────────────────────────────────────────
1044
+ // ── Internal helpers ────────────────────────────────────────────────
1139
1045
 
1140
1046
  /**
1141
- * Truncate a schedule end it early by deleting facts with
1142
- * `period_start > new_end_date`, along with any stale draft entries they
1143
- * produced. Historical posted facts are preserved.
1144
- *
1145
- * `new_end_date` must be a month-end date (service enforces this).
1047
+ * Run a typed GraphQL query against the per-graph endpoint and
1048
+ * translate ClientError into a readable facade error.
1146
1049
  */
1147
- async truncateSchedule(
1050
+ private async gqlQuery<TData, TVars extends object, TResult>(
1148
1051
  graphId: string,
1149
- structureId: string,
1150
- options: TruncateScheduleOptions
1151
- ): Promise<TruncateScheduleResult> {
1152
- const body: TruncateScheduleRequest = {
1153
- new_end_date: options.newEndDate,
1154
- reason: options.reason,
1155
- }
1156
-
1157
- const response = await truncateSchedule({
1158
- path: { graph_id: graphId, structure_id: structureId },
1159
- body,
1160
- })
1161
-
1162
- if (response.error) {
1163
- throw new Error(`Truncate schedule failed: ${JSON.stringify(response.error)}`)
1164
- }
1165
-
1166
- const data = response.data as TruncateScheduleResponse
1167
- return {
1168
- structureId: data.structure_id,
1169
- newEndDate: data.new_end_date,
1170
- factsDeleted: data.facts_deleted,
1171
- reason: data.reason,
1052
+ document: TypedDocumentNode<TData, TVars>,
1053
+ variables: TVars | undefined,
1054
+ label: string,
1055
+ pick: (data: TData) => TResult
1056
+ ): Promise<TResult> {
1057
+ try {
1058
+ const client = this.gql.get(graphId)
1059
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1060
+ const raw = client.request as (doc: unknown, vars?: unknown) => Promise<any>
1061
+ // graphql-request's overloads don't cleanly resolve for generic
1062
+ // helpers wrapping codegen's `Exact<>` var types, so we bypass
1063
+ // the typed overload with a narrow cast of `request` itself.
1064
+ const data = (await raw.call(client, document, variables)) as TData
1065
+ return pick(data)
1066
+ } catch (err) {
1067
+ if (err instanceof ClientError) {
1068
+ throw new Error(`${label} failed: ${JSON.stringify(err.response.errors ?? err.message)}`)
1069
+ }
1070
+ throw err
1172
1071
  }
1173
1072
  }
1174
1073
 
1175
1074
  /**
1176
- * Create a manual draft closing entry with arbitrary balanced line items.
1177
- * Not tied to a schedule used for disposals, adjustments, and other
1178
- * one-off closing events.
1179
- *
1180
- * Line items must sum to balanced debits/credits. Rejects entries
1181
- * targeting an already-closed period.
1075
+ * Await an SDK-generated `opXxx(...)` call, throw a readable error on
1076
+ * non-2xx, and return the `OperationEnvelope` on success.
1182
1077
  */
1183
- async createManualClosingEntry(
1184
- graphId: string,
1185
- options: CreateManualClosingEntryOptions
1186
- ): Promise<ClosingEntry> {
1187
- const body: CreateManualClosingEntryRequest = {
1188
- posting_date: options.postingDate,
1189
- memo: options.memo,
1190
- entry_type: options.entryType,
1191
- line_items: options.lineItems.map((li) => ({
1192
- element_id: li.elementId,
1193
- debit_amount: li.debitAmount ?? 0,
1194
- credit_amount: li.creditAmount ?? 0,
1195
- description: li.description ?? null,
1196
- })),
1078
+ private async callOperation(
1079
+ label: string,
1080
+ call: Promise<{ data?: OperationEnvelope; error?: unknown }>
1081
+ ): Promise<OperationEnvelope> {
1082
+ const response = await call
1083
+ if (response.error !== undefined) {
1084
+ throw new Error(`${label} failed: ${JSON.stringify(response.error)}`)
1197
1085
  }
1198
-
1199
- const response = await createManualClosingEntry({
1200
- path: { graph_id: graphId },
1201
- body,
1202
- })
1203
-
1204
- if (response.error) {
1205
- throw new Error(`Create manual closing entry failed: ${JSON.stringify(response.error)}`)
1086
+ if (response.data === undefined) {
1087
+ throw new Error(`${label} failed: empty response`)
1206
1088
  }
1207
-
1208
- return toClosingEntry(response.data as ClosingEntryResponse)
1089
+ return response.data
1209
1090
  }
1210
1091
  }
1211
1092
 
1212
- // ── Internal helpers ──────────────────────────────────────────────────
1093
+ // ── Module-private conversion helpers ─────────────────────────────────
1213
1094
 
1214
- function toClosingEntry(data: ClosingEntryResponse): ClosingEntry {
1095
+ function rawToClosingEntry(data: RawClosingEntryResult): ClosingEntry {
1215
1096
  return {
1216
- outcome: data.outcome as ClosingEntryOutcome,
1097
+ outcome: data.outcome,
1217
1098
  entryId: data.entry_id ?? null,
1218
1099
  status: data.status ?? null,
1219
1100
  postingDate: data.posting_date ?? null,
@@ -1225,20 +1106,20 @@ function toClosingEntry(data: ClosingEntryResponse): ClosingEntry {
1225
1106
  }
1226
1107
  }
1227
1108
 
1228
- function toFiscalCalendarState(data: FiscalCalendarResponse): FiscalCalendarState {
1109
+ function rawFiscalCalendarToCamel(raw: RawFiscalCalendar): LedgerFiscalCalendar {
1229
1110
  return {
1230
- graphId: data.graph_id,
1231
- fiscalYearStartMonth: data.fiscal_year_start_month,
1232
- closedThrough: data.closed_through ?? null,
1233
- closeTarget: data.close_target ?? null,
1234
- gapPeriods: data.gap_periods ?? 0,
1235
- catchUpSequence: data.catch_up_sequence ?? [],
1236
- closeableNow: data.closeable_now ?? false,
1237
- blockers: data.blockers ?? [],
1238
- lastCloseAt: data.last_close_at ?? null,
1239
- initializedAt: data.initialized_at ?? null,
1240
- lastSyncAt: data.last_sync_at ?? null,
1241
- periods: (data.periods ?? []).map((p) => ({
1111
+ graphId: raw.graph_id,
1112
+ fiscalYearStartMonth: raw.fiscal_year_start_month,
1113
+ closedThrough: raw.closed_through ?? null,
1114
+ closeTarget: raw.close_target ?? null,
1115
+ gapPeriods: raw.gap_periods ?? 0,
1116
+ catchUpSequence: raw.catch_up_sequence ?? [],
1117
+ closeableNow: raw.closeable_now ?? false,
1118
+ blockers: raw.blockers ?? [],
1119
+ lastCloseAt: raw.last_close_at ?? null,
1120
+ initializedAt: raw.initialized_at ?? null,
1121
+ lastSyncAt: raw.last_sync_at ?? null,
1122
+ periods: (raw.periods ?? []).map((p) => ({
1242
1123
  name: p.name,
1243
1124
  startDate: p.start_date,
1244
1125
  endDate: p.end_date,