@ikatec/digisac-api-sdk 4.8.0 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/README.md +36 -12
  2. package/dist/apis/absence/AbsenceApi.cjs +12 -0
  3. package/dist/apis/absence/AbsenceApi.cjs.map +1 -1
  4. package/dist/apis/absence/AbsenceApi.d.ts +9 -0
  5. package/dist/apis/absence/AbsenceApi.d.ts.map +1 -1
  6. package/dist/apis/absence/AbsenceApi.mjs +12 -0
  7. package/dist/apis/absence/AbsenceApi.mjs.map +1 -1
  8. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.cjs +8 -0
  9. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.cjs.map +1 -1
  10. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.d.ts +8 -1
  11. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.d.ts.map +1 -1
  12. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.mjs +8 -0
  13. package/dist/apis/acceptanceTerms/AcceptanceTermsApi.mjs.map +1 -1
  14. package/dist/apis/accounts/AccountsApi.cjs +5 -0
  15. package/dist/apis/accounts/AccountsApi.cjs.map +1 -1
  16. package/dist/apis/accounts/AccountsApi.d.ts +5 -0
  17. package/dist/apis/accounts/AccountsApi.d.ts.map +1 -1
  18. package/dist/apis/accounts/AccountsApi.mjs +5 -0
  19. package/dist/apis/accounts/AccountsApi.mjs.map +1 -1
  20. package/dist/apis/accounts/types.d.ts +2 -0
  21. package/dist/apis/accounts/types.d.ts.map +1 -1
  22. package/dist/apis/activityLog/ActivityLogApi.cjs +12 -0
  23. package/dist/apis/activityLog/ActivityLogApi.cjs.map +1 -1
  24. package/dist/apis/activityLog/ActivityLogApi.d.ts +8 -0
  25. package/dist/apis/activityLog/ActivityLogApi.d.ts.map +1 -1
  26. package/dist/apis/activityLog/ActivityLogApi.mjs +12 -0
  27. package/dist/apis/activityLog/ActivityLogApi.mjs.map +1 -1
  28. package/dist/apis/categories/CategoriesApi.cjs +7 -0
  29. package/dist/apis/categories/CategoriesApi.cjs.map +1 -1
  30. package/dist/apis/categories/CategoriesApi.d.ts +7 -1
  31. package/dist/apis/categories/CategoriesApi.d.ts.map +1 -1
  32. package/dist/apis/categories/CategoriesApi.mjs +7 -0
  33. package/dist/apis/categories/CategoriesApi.mjs.map +1 -1
  34. package/dist/apis/clientFeedback/ClientFeedbackApi.cjs +5 -1
  35. package/dist/apis/clientFeedback/ClientFeedbackApi.cjs.map +1 -1
  36. package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts +5 -1
  37. package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts.map +1 -1
  38. package/dist/apis/clientFeedback/ClientFeedbackApi.mjs +5 -1
  39. package/dist/apis/clientFeedback/ClientFeedbackApi.mjs.map +1 -1
  40. package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs +8 -0
  41. package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs.map +1 -1
  42. package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts +8 -1
  43. package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts.map +1 -1
  44. package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs +8 -0
  45. package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs.map +1 -1
  46. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs +7 -0
  47. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs.map +1 -1
  48. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts +7 -1
  49. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts.map +1 -1
  50. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs +7 -0
  51. package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs.map +1 -1
  52. package/dist/apis/contacts/ContactsApi.cjs +16 -3
  53. package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
  54. package/dist/apis/contacts/ContactsApi.d.ts +16 -5
  55. package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
  56. package/dist/apis/contacts/ContactsApi.mjs +16 -3
  57. package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
  58. package/dist/apis/contacts/types.d.ts +6 -1
  59. package/dist/apis/contacts/types.d.ts.map +1 -1
  60. package/dist/apis/creditMovements/CreditMovementsApi.cjs +17 -0
  61. package/dist/apis/creditMovements/CreditMovementsApi.cjs.map +1 -1
  62. package/dist/apis/creditMovements/CreditMovementsApi.d.ts +11 -0
  63. package/dist/apis/creditMovements/CreditMovementsApi.d.ts.map +1 -1
  64. package/dist/apis/creditMovements/CreditMovementsApi.mjs +17 -0
  65. package/dist/apis/creditMovements/CreditMovementsApi.mjs.map +1 -1
  66. package/dist/apis/customFields/CustomFieldsApi.cjs +7 -0
  67. package/dist/apis/customFields/CustomFieldsApi.cjs.map +1 -1
  68. package/dist/apis/customFields/CustomFieldsApi.d.ts +7 -1
  69. package/dist/apis/customFields/CustomFieldsApi.d.ts.map +1 -1
  70. package/dist/apis/customFields/CustomFieldsApi.mjs +7 -0
  71. package/dist/apis/customFields/CustomFieldsApi.mjs.map +1 -1
  72. package/dist/apis/departments/DepartmentsApi.cjs +21 -0
  73. package/dist/apis/departments/DepartmentsApi.cjs.map +1 -1
  74. package/dist/apis/departments/DepartmentsApi.d.ts +20 -2
  75. package/dist/apis/departments/DepartmentsApi.d.ts.map +1 -1
  76. package/dist/apis/departments/DepartmentsApi.mjs +21 -0
  77. package/dist/apis/departments/DepartmentsApi.mjs.map +1 -1
  78. package/dist/apis/departments/types.d.ts +5 -0
  79. package/dist/apis/departments/types.d.ts.map +1 -1
  80. package/dist/apis/distribution/DistributionApi.cjs +7 -0
  81. package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
  82. package/dist/apis/distribution/DistributionApi.d.ts +7 -1
  83. package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
  84. package/dist/apis/distribution/DistributionApi.mjs +7 -0
  85. package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
  86. package/dist/apis/files/FilesApi.cjs +49 -0
  87. package/dist/apis/files/FilesApi.cjs.map +1 -1
  88. package/dist/apis/files/FilesApi.d.ts +45 -2
  89. package/dist/apis/files/FilesApi.d.ts.map +1 -1
  90. package/dist/apis/files/FilesApi.mjs +49 -0
  91. package/dist/apis/files/FilesApi.mjs.map +1 -1
  92. package/dist/apis/files/types.d.ts +45 -7
  93. package/dist/apis/files/types.d.ts.map +1 -1
  94. package/dist/apis/holiday/HolidayApi.cjs +7 -0
  95. package/dist/apis/holiday/HolidayApi.cjs.map +1 -1
  96. package/dist/apis/holiday/HolidayApi.d.ts +7 -1
  97. package/dist/apis/holiday/HolidayApi.d.ts.map +1 -1
  98. package/dist/apis/holiday/HolidayApi.mjs +7 -0
  99. package/dist/apis/holiday/HolidayApi.mjs.map +1 -1
  100. package/dist/apis/integrations/IntegrationsApi.cjs +7 -0
  101. package/dist/apis/integrations/IntegrationsApi.cjs.map +1 -1
  102. package/dist/apis/integrations/IntegrationsApi.d.ts +7 -1
  103. package/dist/apis/integrations/IntegrationsApi.d.ts.map +1 -1
  104. package/dist/apis/integrations/IntegrationsApi.mjs +7 -0
  105. package/dist/apis/integrations/IntegrationsApi.mjs.map +1 -1
  106. package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs +7 -0
  107. package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs.map +1 -1
  108. package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts +7 -1
  109. package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts.map +1 -1
  110. package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs +7 -0
  111. package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs.map +1 -1
  112. package/dist/apis/interactiveMessages/types.d.ts +61 -3
  113. package/dist/apis/interactiveMessages/types.d.ts.map +1 -1
  114. package/dist/apis/kanbanBoards/KanbanBoardsApi.cjs +7 -0
  115. package/dist/apis/kanbanBoards/KanbanBoardsApi.cjs.map +1 -1
  116. package/dist/apis/kanbanBoards/KanbanBoardsApi.d.ts +7 -1
  117. package/dist/apis/kanbanBoards/KanbanBoardsApi.d.ts.map +1 -1
  118. package/dist/apis/kanbanBoards/KanbanBoardsApi.mjs +7 -0
  119. package/dist/apis/kanbanBoards/KanbanBoardsApi.mjs.map +1 -1
  120. package/dist/apis/kanbanCards/KanbanCardsApi.cjs +7 -0
  121. package/dist/apis/kanbanCards/KanbanCardsApi.cjs.map +1 -1
  122. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts +7 -1
  123. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts.map +1 -1
  124. package/dist/apis/kanbanCards/KanbanCardsApi.mjs +7 -0
  125. package/dist/apis/kanbanCards/KanbanCardsApi.mjs.map +1 -1
  126. package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs +8 -0
  127. package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs.map +1 -1
  128. package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts +8 -1
  129. package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts.map +1 -1
  130. package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs +8 -0
  131. package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs.map +1 -1
  132. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs +8 -0
  133. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs.map +1 -1
  134. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts +8 -1
  135. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts.map +1 -1
  136. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs +8 -0
  137. package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs.map +1 -1
  138. package/dist/apis/messages/MessagesApi.cjs +23 -3
  139. package/dist/apis/messages/MessagesApi.cjs.map +1 -1
  140. package/dist/apis/messages/MessagesApi.d.ts +19 -3
  141. package/dist/apis/messages/MessagesApi.d.ts.map +1 -1
  142. package/dist/apis/messages/MessagesApi.mjs +23 -3
  143. package/dist/apis/messages/MessagesApi.mjs.map +1 -1
  144. package/dist/apis/messages/types.d.ts +3 -0
  145. package/dist/apis/messages/types.d.ts.map +1 -1
  146. package/dist/apis/notifications/NotificationsApi.cjs +7 -0
  147. package/dist/apis/notifications/NotificationsApi.cjs.map +1 -1
  148. package/dist/apis/notifications/NotificationsApi.d.ts +7 -1
  149. package/dist/apis/notifications/NotificationsApi.d.ts.map +1 -1
  150. package/dist/apis/notifications/NotificationsApi.mjs +7 -0
  151. package/dist/apis/notifications/NotificationsApi.mjs.map +1 -1
  152. package/dist/apis/organizations/OrganizationsApi.cjs +7 -0
  153. package/dist/apis/organizations/OrganizationsApi.cjs.map +1 -1
  154. package/dist/apis/organizations/OrganizationsApi.d.ts +7 -1
  155. package/dist/apis/organizations/OrganizationsApi.d.ts.map +1 -1
  156. package/dist/apis/organizations/OrganizationsApi.mjs +7 -0
  157. package/dist/apis/organizations/OrganizationsApi.mjs.map +1 -1
  158. package/dist/apis/people/PeopleApi.cjs +7 -0
  159. package/dist/apis/people/PeopleApi.cjs.map +1 -1
  160. package/dist/apis/people/PeopleApi.d.ts +7 -1
  161. package/dist/apis/people/PeopleApi.d.ts.map +1 -1
  162. package/dist/apis/people/PeopleApi.mjs +7 -0
  163. package/dist/apis/people/PeopleApi.mjs.map +1 -1
  164. package/dist/apis/planAiHistory/PlanAiHistoryApi.cjs +16 -0
  165. package/dist/apis/planAiHistory/PlanAiHistoryApi.cjs.map +1 -1
  166. package/dist/apis/planAiHistory/PlanAiHistoryApi.d.ts +10 -0
  167. package/dist/apis/planAiHistory/PlanAiHistoryApi.d.ts.map +1 -1
  168. package/dist/apis/planAiHistory/PlanAiHistoryApi.mjs +16 -0
  169. package/dist/apis/planAiHistory/PlanAiHistoryApi.mjs.map +1 -1
  170. package/dist/apis/roles/RolesApi.cjs +7 -0
  171. package/dist/apis/roles/RolesApi.cjs.map +1 -1
  172. package/dist/apis/roles/RolesApi.d.ts +6 -1
  173. package/dist/apis/roles/RolesApi.d.ts.map +1 -1
  174. package/dist/apis/roles/RolesApi.mjs +7 -0
  175. package/dist/apis/roles/RolesApi.mjs.map +1 -1
  176. package/dist/apis/services/ServicesApi.cjs +68 -0
  177. package/dist/apis/services/ServicesApi.cjs.map +1 -1
  178. package/dist/apis/services/ServicesApi.d.ts +53 -1
  179. package/dist/apis/services/ServicesApi.d.ts.map +1 -1
  180. package/dist/apis/services/ServicesApi.mjs +68 -0
  181. package/dist/apis/services/ServicesApi.mjs.map +1 -1
  182. package/dist/apis/services/types.d.ts +68 -1
  183. package/dist/apis/services/types.d.ts.map +1 -1
  184. package/dist/apis/stickers/StickersApi.cjs +20 -0
  185. package/dist/apis/stickers/StickersApi.cjs.map +1 -1
  186. package/dist/apis/stickers/StickersApi.d.ts +15 -0
  187. package/dist/apis/stickers/StickersApi.d.ts.map +1 -1
  188. package/dist/apis/stickers/StickersApi.mjs +20 -0
  189. package/dist/apis/stickers/StickersApi.mjs.map +1 -1
  190. package/dist/apis/tags/TagsApi.cjs +7 -0
  191. package/dist/apis/tags/TagsApi.cjs.map +1 -1
  192. package/dist/apis/tags/TagsApi.d.ts +7 -1
  193. package/dist/apis/tags/TagsApi.d.ts.map +1 -1
  194. package/dist/apis/tags/TagsApi.mjs +7 -0
  195. package/dist/apis/tags/TagsApi.mjs.map +1 -1
  196. package/dist/apis/timetable/TimetableApi.cjs +7 -0
  197. package/dist/apis/timetable/TimetableApi.cjs.map +1 -1
  198. package/dist/apis/timetable/TimetableApi.d.ts +7 -1
  199. package/dist/apis/timetable/TimetableApi.d.ts.map +1 -1
  200. package/dist/apis/timetable/TimetableApi.mjs +7 -0
  201. package/dist/apis/timetable/TimetableApi.mjs.map +1 -1
  202. package/dist/apis/users/UsersApi.cjs +8 -0
  203. package/dist/apis/users/UsersApi.cjs.map +1 -1
  204. package/dist/apis/users/UsersApi.d.ts +8 -1
  205. package/dist/apis/users/UsersApi.d.ts.map +1 -1
  206. package/dist/apis/users/UsersApi.mjs +8 -0
  207. package/dist/apis/users/UsersApi.mjs.map +1 -1
  208. package/dist/apis/users/types.d.ts +15 -0
  209. package/dist/apis/users/types.d.ts.map +1 -1
  210. package/dist/apis/webhooks/WebhooksApi.cjs +7 -0
  211. package/dist/apis/webhooks/WebhooksApi.cjs.map +1 -1
  212. package/dist/apis/webhooks/WebhooksApi.d.ts +7 -1
  213. package/dist/apis/webhooks/WebhooksApi.d.ts.map +1 -1
  214. package/dist/apis/webhooks/WebhooksApi.mjs +7 -0
  215. package/dist/apis/webhooks/WebhooksApi.mjs.map +1 -1
  216. package/dist/core/ApiClient.d.ts +14 -0
  217. package/dist/core/ApiClient.d.ts.map +1 -1
  218. package/dist/core/ArchivableCrudApi.cjs.map +1 -1
  219. package/dist/core/ArchivableCrudApi.d.ts +1 -1
  220. package/dist/core/ArchivableCrudApi.d.ts.map +1 -1
  221. package/dist/core/ArchivableCrudApi.mjs.map +1 -1
  222. package/dist/core/BaseApiClient.cjs +24 -3
  223. package/dist/core/BaseApiClient.cjs.map +1 -1
  224. package/dist/core/BaseApiClient.d.ts +14 -0
  225. package/dist/core/BaseApiClient.d.ts.map +1 -1
  226. package/dist/core/BaseApiClient.mjs +24 -3
  227. package/dist/core/BaseApiClient.mjs.map +1 -1
  228. package/dist/core/requestExternal.cjs +33 -0
  229. package/dist/core/requestExternal.cjs.map +1 -0
  230. package/dist/core/requestExternal.d.ts +9 -0
  231. package/dist/core/requestExternal.d.ts.map +1 -0
  232. package/dist/core/requestExternal.mjs +32 -0
  233. package/dist/core/requestExternal.mjs.map +1 -0
  234. package/package.json +1 -1
  235. package/src/apis/absence/AbsenceApi.ts +19 -0
  236. package/src/apis/acceptanceTerms/AcceptanceTermsApi.ts +13 -1
  237. package/src/apis/accounts/AccountsApi.ts +5 -0
  238. package/src/apis/accounts/types.ts +2 -0
  239. package/src/apis/activityLog/ActivityLogApi.ts +18 -0
  240. package/src/apis/categories/CategoriesApi.ts +12 -1
  241. package/src/apis/clientFeedback/ClientFeedbackApi.ts +5 -1
  242. package/src/apis/contactBlockLists/ContactBlockListsApi.ts +13 -1
  243. package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.ts +12 -1
  244. package/src/apis/contacts/ContactsApi.test.ts +20 -4
  245. package/src/apis/contacts/ContactsApi.ts +24 -5
  246. package/src/apis/contacts/types.ts +7 -1
  247. package/src/apis/creditMovements/CreditMovementsApi.ts +27 -0
  248. package/src/apis/customFields/CustomFieldsApi.ts +12 -1
  249. package/src/apis/departments/DepartmentsApi.test.ts +88 -0
  250. package/src/apis/departments/DepartmentsApi.ts +41 -2
  251. package/src/apis/departments/types.ts +6 -0
  252. package/src/apis/distribution/DistributionApi.ts +12 -1
  253. package/src/apis/files/FilesApi.test.ts +122 -0
  254. package/src/apis/files/FilesApi.ts +107 -2
  255. package/src/apis/files/types.ts +48 -7
  256. package/src/apis/holiday/HolidayApi.ts +16 -1
  257. package/src/apis/integrations/IntegrationsApi.ts +12 -1
  258. package/src/apis/interactiveMessages/InteractiveMessagesApi.ts +12 -1
  259. package/src/apis/interactiveMessages/types.ts +67 -1
  260. package/src/apis/kanbanBoards/KanbanBoardsApi.ts +12 -1
  261. package/src/apis/kanbanCards/KanbanCardsApi.ts +12 -1
  262. package/src/apis/knowledgeBase/KnowledgeBaseApi.ts +13 -1
  263. package/src/apis/knowledgeBaseItem/KnowledgeBaseItemApi.ts +13 -1
  264. package/src/apis/messages/MessagesApi.test.ts +13 -7
  265. package/src/apis/messages/MessagesApi.ts +29 -3
  266. package/src/apis/messages/types.ts +3 -0
  267. package/src/apis/notifications/NotificationsApi.ts +12 -1
  268. package/src/apis/organizations/OrganizationsApi.ts +12 -1
  269. package/src/apis/payloads.test-d.ts +275 -0
  270. package/src/apis/payloads.test.ts +41 -0
  271. package/src/apis/people/PeopleApi.ts +16 -1
  272. package/src/apis/planAiHistory/PlanAiHistoryApi.ts +26 -0
  273. package/src/apis/roles/RolesApi.ts +10 -1
  274. package/src/apis/services/ServicesApi.test.ts +45 -0
  275. package/src/apis/services/ServicesApi.ts +91 -1
  276. package/src/apis/services/types.ts +63 -1
  277. package/src/apis/stickers/StickersApi.ts +32 -0
  278. package/src/apis/tags/TagsApi.ts +16 -1
  279. package/src/apis/timetable/TimetableApi.ts +12 -1
  280. package/src/apis/users/UsersApi.ts +17 -1
  281. package/src/apis/users/types.ts +11 -0
  282. package/src/apis/webhooks/WebhooksApi.ts +16 -1
  283. package/src/core/ApiClient.ts +24 -0
  284. package/src/core/ArchivableCrudApi.ts +2 -1
  285. package/src/core/BaseApiClient.test.ts +98 -0
  286. package/src/core/BaseApiClient.ts +49 -4
  287. package/src/core/requestExternal.ts +43 -0
@@ -1,6 +1,12 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
3
- import type { Service, CreateServicePayload, ServiceLimits, UpdateServicePayload } from './types'
3
+ import type {
4
+ Service,
5
+ CreateServicePayload,
6
+ ServiceLimits,
7
+ UpdateServicePayload,
8
+ ArchiveServicePayload,
9
+ } from './types'
4
10
 
5
11
  export class ServicesApi extends ArchivableCrudApi<
6
12
  Service,
@@ -19,4 +25,88 @@ export class ServicesApi extends ArchivableCrudApi<
19
25
  getLimits(headers?: Record<string, string>): Promise<ServiceLimits> {
20
26
  return this.client.get<ServiceLimits>(`${this.basePath}/limits`, headers)
21
27
  }
28
+
29
+ /**
30
+ * @deprecated The backend does not archive a connection by `PUT { archivedAt }`: the PUT
31
+ * requires `type` (400 without it) and ignores `archivedAt`. Use {@link archiveWithValidation}.
32
+ */
33
+ override archive(id: string, headers?: Record<string, string>): Promise<Service> {
34
+ return super.archive(id, headers)
35
+ }
36
+
37
+ /**
38
+ * @deprecated The backend does not restore a connection by `PUT { archivedAt: null }`: the PUT
39
+ * requires `type` (400 without it) and ignores `archivedAt`. Use {@link archiveWithValidation}.
40
+ */
41
+ override unarchive(id: string, headers?: Record<string, string>): Promise<Service> {
42
+ return super.unarchive(id, headers)
43
+ }
44
+
45
+ /**
46
+ * Archives (`archive: true`) or restores (`archive: false`) a connection through the route used
47
+ * by the UI. Archiving shuts the connection down, closes its open tickets, marks its schedules
48
+ * as done and cancels its campaigns; restoring checks the plan limit of the `type` (400 when it
49
+ * is full).
50
+ *
51
+ * Resolves `{}` before the work is done (the route does not await it): poll `getById` until
52
+ * `archivedAt` changes. Send the `type` of the connection: without it the request is never
53
+ * answered.
54
+ * @permissions services.disabled
55
+ */
56
+ archiveWithValidation(
57
+ id: string,
58
+ body: ArchiveServicePayload,
59
+ headers?: Record<string, string>,
60
+ ): Promise<Record<string, never>> {
61
+ return this.client.post<Record<string, never>, ArchiveServicePayload>(
62
+ `${this.basePath}/${id}/archive`,
63
+ body,
64
+ headers,
65
+ )
66
+ }
67
+
68
+ /**
69
+ * Takes the session of the connection over to this server. Resolves the text `'OK'`; an
70
+ * unknown id answers 404 `Service not found.`.
71
+ * @permissions services.operate
72
+ */
73
+ takeover(id: string, headers?: Record<string, string>): Promise<'OK'> {
74
+ return this.client.post<'OK'>(`${this.basePath}/${id}/takeover`, undefined, headers)
75
+ }
76
+
77
+ /**
78
+ * Logs the connection out of the messaging service. Resolves the text `'OK'`; an unknown id
79
+ * answers 404 `Service not found.`.
80
+ * @permissions services.operate
81
+ */
82
+ logout(id: string, headers?: Record<string, string>): Promise<'OK'> {
83
+ return this.client.post<'OK'>(`${this.basePath}/${id}/logout`, undefined, headers)
84
+ }
85
+
86
+ /**
87
+ * Shuts the connection down. Resolves the text `'OK'`; an unknown id answers 404
88
+ * `Service not found.`.
89
+ * @permissions services.operate
90
+ */
91
+ shutdown(id: string, headers?: Record<string, string>): Promise<'OK'> {
92
+ return this.client.post<'OK'>(`${this.basePath}/${id}/shutdown`, undefined, headers)
93
+ }
94
+
95
+ /**
96
+ * Starts the connection. Resolves the text `'OK'`; an unknown id answers 404
97
+ * `Service not found.`.
98
+ * @permissions services.operate
99
+ */
100
+ start(id: string, headers?: Record<string, string>): Promise<'OK'> {
101
+ return this.client.post<'OK'>(`${this.basePath}/${id}/start`, undefined, headers)
102
+ }
103
+
104
+ /**
105
+ * Restarts the connection. Resolves the text `'OK'`; an unknown id answers 404
106
+ * `Service not found.`.
107
+ * @permissions services.operate
108
+ */
109
+ restart(id: string, headers?: Record<string, string>): Promise<'OK'> {
110
+ return this.client.post<'OK'>(`${this.basePath}/${id}/restart`, undefined, headers)
111
+ }
22
112
  }
@@ -47,6 +47,52 @@ export type ServiceConnectionStatus = {
47
47
  timestamp?: string
48
48
  }
49
49
 
50
+ /**
51
+ * `data.webchat` of a Webchat service. Every key is optional: the object only fills up once the
52
+ * service is set up, and older services lack the keys added later (e.g. `instagram`). The backend
53
+ * stores what the forms send without normalizing it, so `null` and `{}` show up where the web
54
+ * forms write them.
55
+ */
56
+ export type WebchatServiceData = {
57
+ /** Widget id in the webchat API; the page link is `${webchatUrl}/${id}`. Absent before setup. */
58
+ id?: string
59
+ name?: string | null
60
+ /** WhatsApp channel of the widget: DDI + number, without mask. `null` once cleared. */
61
+ phone?: string | null
62
+ /** Telegram channel of the widget, without `@`. `null` once cleared. */
63
+ telegram?: string | null
64
+ /** Instagram channel of the widget, without `@`. `null` once cleared. */
65
+ instagram?: string | null
66
+ isOpenForm?: boolean
67
+ isOpenChat?: boolean
68
+ /** `{}` when no picture was picked in the legacy form. */
69
+ file?: { base64Url?: string; mimetype?: string; fileName?: string }
70
+ googleId?: string | null
71
+ desktopChatOpen?: boolean
72
+ mobileChatOpen?: boolean
73
+ getClientData?: boolean
74
+ closeTicketByClient?: boolean
75
+ phoneClient?: boolean
76
+ phoneClientRequired?: boolean
77
+ clientName?: boolean
78
+ clientNameRequired?: boolean
79
+ /** `null` or `{}` when no custom field is asked for. */
80
+ customField?: { id?: string; name?: string } | null
81
+ customFieldRequired?: boolean
82
+ custom?: {
83
+ webchatBalloon?: boolean
84
+ webchatBalloonText?: string | null
85
+ webchatColor?: string
86
+ /** File id. */
87
+ logo?: string
88
+ /** File id. */
89
+ profilePhoto?: string
90
+ /** File id. */
91
+ webchatIcon?: string
92
+ typeIcon?: string
93
+ }
94
+ }
95
+
50
96
  export type ServiceData = {
51
97
  // ─── Common ───────────────────────────────────────────────────────────────
52
98
  token?: string
@@ -106,7 +152,7 @@ export type ServiceData = {
106
152
  mimetype: string
107
153
  fileName: string
108
154
  }
109
- webchat?: Record<string, unknown>
155
+ webchat?: WebchatServiceData
110
156
 
111
157
  // ─── Other ────────────────────────────────────────────────────────────────
112
158
  driverId?: string
@@ -207,6 +253,8 @@ export type ServiceRelationships = 'bot' | 'defaultDepartment'
207
253
  export type CreateServicePayload = {
208
254
  name: string
209
255
  type: ServiceType
256
+ /** Send `null`: without it the duplicate-name check never answers (SUS-3784). */
257
+ archivedAt?: null
210
258
  data?: ServiceData
211
259
  settings?: ServiceSettings
212
260
  defaultDepartmentId?: string
@@ -214,14 +262,28 @@ export type CreateServicePayload = {
214
262
  }
215
263
 
216
264
  export type UpdateServicePayload = {
265
+ /** Required by the backend (400 without it), although optional here for compatibility. */
266
+ type?: ServiceType
217
267
  name?: string
218
268
  data?: ServiceData
219
269
  settings?: ServiceSettings
220
270
  defaultDepartmentId?: string | null
221
271
  botId?: string | null
272
+ /**
273
+ * Ignored by the backend (archive through `ServicesApi.archiveWithValidation`). Send `null`
274
+ * with a `name`: without `archivedAt` the duplicate-name check never answers (SUS-3784).
275
+ */
222
276
  archivedAt?: string | null
223
277
  }
224
278
 
279
+ /** Body of `ServicesApi.archiveWithValidation`. */
280
+ export type ArchiveServicePayload = {
281
+ /** `true` archives, `false` restores. */
282
+ archive: boolean
283
+ /** The type of the connection, used by the plan limit check (the request hangs without it). */
284
+ type: ServiceType
285
+ }
286
+
225
287
  export type ServiceLimit = {
226
288
  used: number
227
289
  total: number
@@ -1,9 +1,41 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
+ import type { GetByIdQuery } from '../../core/types'
3
4
  import type { Sticker, CreateStickerPayload, UpdateStickerPayload } from './types'
4
5
 
6
+ /**
7
+ * Stickers of the account. The backend serves `GET /stickers` and `POST /stickers` (plus
8
+ * `GET /stickers/exist`, `POST /stickers/add` and `DELETE /stickers/remove/:stickerId`); there is
9
+ * no read by id, update nor `DELETE /stickers/:id`.
10
+ */
5
11
  export class StickersApi extends BaseCrudApi<Sticker, CreateStickerPayload, UpdateStickerPayload> {
6
12
  constructor(client: ApiClient) {
7
13
  super(client, '/stickers')
8
14
  }
15
+
16
+ /** @deprecated Not served: the backend has no GET /stickers/:id, so this route answers 404. */
17
+ override getById(
18
+ id: string,
19
+ query?: GetByIdQuery<Sticker>,
20
+ headers?: Record<string, string>,
21
+ ): Promise<Sticker> {
22
+ return super.getById(id, query, headers)
23
+ }
24
+
25
+ /** @deprecated Not served: the backend has no PUT /stickers/:id, so this route answers 404. */
26
+ override updateById(
27
+ id: string,
28
+ body: UpdateStickerPayload,
29
+ headers?: Record<string, string>,
30
+ ): Promise<Sticker> {
31
+ return super.updateById(id, body, headers)
32
+ }
33
+
34
+ /**
35
+ * @deprecated Not served: the backend has no DELETE /stickers/:id (it removes a sticker by
36
+ * `DELETE /stickers/remove/:stickerId`), so this route answers 404.
37
+ */
38
+ override deleteById(id: string, headers?: Record<string, string>): Promise<Sticker> {
39
+ return super.deleteById(id, headers)
40
+ }
9
41
  }
@@ -1,4 +1,5 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
+ import type { ListQuery } from '../../core/types'
2
3
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
4
  import type { ContactsSelection } from '../contacts/types'
4
5
  import type {
@@ -9,11 +10,25 @@ import type {
9
10
  UpdateTagPayload,
10
11
  } from './types'
11
12
 
12
- export class TagsApi extends BaseCrudApi<Tag, CreateTagPayload, UpdateTagPayload> {
13
+ export class TagsApi extends BaseCrudApi<
14
+ Tag,
15
+ CreateTagPayload,
16
+ UpdateTagPayload,
17
+ ListQuery<Tag>,
18
+ 'OK'
19
+ > {
13
20
  constructor(client: ApiClient) {
14
21
  super(client, '/tags')
15
22
  }
16
23
 
24
+ /**
25
+ * Deletes a tag. Resolves the text `'OK'`; an unknown id answers 500.
26
+ * @permissions tags.view OR tags.destroy
27
+ */
28
+ override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
29
+ return super.deleteById(id, headers)
30
+ }
31
+
17
32
  /**
18
33
  * Adds the tag to every contact of the account that matches the selection. Resolves to `'OK'`,
19
34
  * or `false` when every contact already had the tag.
@@ -1,13 +1,24 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
+ import type { ListQuery } from '../../core/types'
2
3
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
4
  import type { Timetable, CreateTimetablePayload, UpdateTimetablePayload } from './types'
4
5
 
5
6
  export class TimetableApi extends BaseCrudApi<
6
7
  Timetable,
7
8
  CreateTimetablePayload,
8
- UpdateTimetablePayload
9
+ UpdateTimetablePayload,
10
+ ListQuery<Timetable>,
11
+ 'OK'
9
12
  > {
10
13
  constructor(client: ApiClient) {
11
14
  super(client, '/timetable')
12
15
  }
16
+
17
+ /**
18
+ * Deletes a timetable. Resolves the text `'OK'`.
19
+ * @permissions timetables.view OR timetables.destroy
20
+ */
21
+ override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
22
+ return super.deleteById(id, headers)
23
+ }
13
24
  }
@@ -1,4 +1,5 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
+ import type { ListQuery } from '../../core/types'
2
3
  import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
3
4
  import type { Contact } from '../contacts/types'
4
5
  import type {
@@ -19,11 +20,26 @@ import type {
19
20
  ValidateUserImportResult,
20
21
  } from './types'
21
22
 
22
- export class UsersApi extends ArchivableCrudApi<User, CreateUserPayload, UpdateUserPayload> {
23
+ export class UsersApi extends ArchivableCrudApi<
24
+ User,
25
+ CreateUserPayload,
26
+ UpdateUserPayload,
27
+ ListQuery<User>,
28
+ 'OK'
29
+ > {
23
30
  constructor(client: ApiClient) {
24
31
  super(client, '/users')
25
32
  }
26
33
 
34
+ /**
35
+ * Deletes a user (removes it as the default user of the contacts and deletes its webhooks first).
36
+ * Resolves the text `'OK'`.
37
+ * @permissions users.destroy
38
+ */
39
+ override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
40
+ return super.deleteById(id, headers)
41
+ }
42
+
27
43
  /**
28
44
  * Archives or restores a user through the dedicated route used by the UI, which also
29
45
  * sets the user offline and ends its sessions. The route enforces the business rules that
@@ -71,7 +71,10 @@ export type CreateUserPayload = {
71
71
  isClientUser?: boolean
72
72
  roles?: string[]
73
73
  departments?: string[]
74
+ /** @deprecated Not read by the backend: restrict the connections with `restrictedServices`. */
74
75
  services?: string[]
76
+ /** Connections the user cannot access, as `{ value: serviceId }`. */
77
+ restrictedServices?: { value: string }[]
75
78
  scheduleId?: string
76
79
  }
77
80
 
@@ -83,7 +86,15 @@ export type UpdateUserPayload = {
83
86
  isClientUser?: boolean
84
87
  roles?: string[]
85
88
  departments?: string[]
89
+ /** @deprecated Not read by the backend: restrict the connections with `restrictedServices`. */
86
90
  services?: string[]
91
+ /**
92
+ * Connections the user cannot access, as `{ value: serviceId }`. Every update without it (and
93
+ * every update of an admin) clears the restrictions of the user.
94
+ */
95
+ restrictedServices?: { value: string }[]
96
+ /** New password of the user. */
97
+ password?: string
87
98
  timetableId?: string | null
88
99
  preferences?: UserPreferences
89
100
  language?: string
@@ -1,9 +1,24 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
+ import type { ListQuery } from '../../core/types'
2
3
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
4
  import type { Webhook, CreateWebhookPayload, UpdateWebhookPayload } from './types'
4
5
 
5
- export class WebhooksApi extends BaseCrudApi<Webhook, CreateWebhookPayload, UpdateWebhookPayload> {
6
+ export class WebhooksApi extends BaseCrudApi<
7
+ Webhook,
8
+ CreateWebhookPayload,
9
+ UpdateWebhookPayload,
10
+ ListQuery<Webhook>,
11
+ 'OK'
12
+ > {
6
13
  constructor(client: ApiClient) {
7
14
  super(client, '/me/webhooks')
8
15
  }
16
+
17
+ /**
18
+ * Deletes a webhook. Resolves the text `'OK'`; an unknown id answers 500.
19
+ * @permissions settingsApi.update
20
+ */
21
+ override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
22
+ return super.deleteById(id, headers)
23
+ }
9
24
  }
@@ -32,4 +32,28 @@ export interface ApiClient {
32
32
  endpoint: string,
33
33
  headers?: Record<string, string>,
34
34
  ): Promise<TResponse>
35
+ /**
36
+ * Like {@link request}, but resolves the raw `Response` of a successful (2xx) call instead of
37
+ * the parsed body, for the routes whose headers matter (`Content-Type`, `Content-Disposition`)
38
+ * or whose body is binary (xlsx, pdf, files). Non-2xx still throws the `ApiError` subclass.
39
+ * Optional: a custom client without it cannot call the API methods that need the raw response.
40
+ */
41
+ requestRaw?<TBody = unknown>(
42
+ method: HttpMethod,
43
+ endpoint: string,
44
+ body?: TBody,
45
+ headers?: Record<string, string>,
46
+ ): Promise<Response>
47
+ /**
48
+ * Sends a request to an absolute URL outside the API (e.g. the signed storage URL of
49
+ * `FilesApi.upload`): no base URL, no `Authorization` header and no JSON serialization, `body`
50
+ * goes as is. Resolves the raw `Response` of a 2xx answer; non-2xx throws the `ApiError`
51
+ * subclass. Optional: without it, the API methods that need it send through the global `fetch`.
52
+ */
53
+ requestExternal?(
54
+ method: HttpMethod,
55
+ url: string,
56
+ body?: BodyInit,
57
+ headers?: Record<string, string>,
58
+ ): Promise<Response>
35
59
  }
@@ -17,7 +17,8 @@ export class ArchivableCrudApi<
17
17
  TCreate,
18
18
  TUpdate extends { archivedAt?: string | null } = TCreate & { archivedAt?: string | null },
19
19
  TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
20
- > extends BaseCrudApi<TResponse, TCreate, TUpdate, TQuery> {
20
+ TDeleteResponse = TResponse,
21
+ > extends BaseCrudApi<TResponse, TCreate, TUpdate, TQuery, TDeleteResponse> {
21
22
  constructor(client: ApiClient, basePath: string) {
22
23
  super(client, basePath)
23
24
  }
@@ -498,4 +498,102 @@ describe('BaseApiClient', () => {
498
498
  errorClass: 'AccessDenied',
499
499
  })
500
500
  })
501
+
502
+ // ── requestRaw ───────────────────────────────────────────────────
503
+
504
+ describe('requestRaw', () => {
505
+ it('resolves the raw Response of a 2xx answer, with the headers and body untouched', async () => {
506
+ const spy = mockFetch({
507
+ ok: true,
508
+ status: 200,
509
+ statusText: 'OK',
510
+ headers: { 'Content-Type': 'application/pdf', 'Content-Disposition': 'attachment' },
511
+ body: '%PDF-1.4',
512
+ })
513
+ client.setAccessToken('token-1')
514
+ const response = await client.requestRaw('GET', '/terms/t-1/export-pdf')
515
+ expect(spy).toHaveBeenCalledWith(
516
+ 'https://api.example.com/terms/t-1/export-pdf',
517
+ expect.objectContaining({
518
+ method: 'GET',
519
+ headers: expect.objectContaining({ Authorization: 'Bearer token-1' }),
520
+ }),
521
+ )
522
+ expect(response.status).toBe(200)
523
+ expect(response.headers.get('Content-Type')).toBe('application/pdf')
524
+ expect(await response.text()).toBe('%PDF-1.4')
525
+ })
526
+
527
+ it('sends the JSON body and the custom headers', async () => {
528
+ const spy = mockFetch({ ok: true, status: 200, statusText: 'OK', body: 'a;b' })
529
+ await client.requestRaw('POST', '/export', { where: { id: '1' } }, { 'X-Custom': 'v' })
530
+ expect(spy).toHaveBeenCalledWith(
531
+ 'https://api.example.com/export',
532
+ expect.objectContaining({
533
+ method: 'POST',
534
+ body: JSON.stringify({ where: { id: '1' } }),
535
+ headers: expect.objectContaining({ 'X-Custom': 'v' }),
536
+ }),
537
+ )
538
+ })
539
+
540
+ it('throws the ApiError subclass of a non-2xx answer', async () => {
541
+ mockFetch({
542
+ ok: false,
543
+ status: 404,
544
+ statusText: 'Not Found',
545
+ headers: { 'Content-Type': 'application/json' },
546
+ body: JSON.stringify({ error: 'NotFoundHttpError', message: 'Route not found.' }),
547
+ })
548
+ await expect(client.requestRaw('GET', '/missing')).rejects.toBeInstanceOf(NotFoundError)
549
+ })
550
+
551
+ it('throws ApiConnectionError when the request fails', async () => {
552
+ vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('fetch failed'))
553
+ await expect(client.requestRaw('GET', '/down')).rejects.toBeInstanceOf(ApiConnectionError)
554
+ })
555
+ })
556
+
557
+ // ── requestExternal ──────────────────────────────────────────────
558
+
559
+ describe('requestExternal', () => {
560
+ const SIGNED_URL = 'https://storage.example.com/acc-1/file-1.csv?X-Amz-Signature=secret'
561
+
562
+ it('sends the body as is to the absolute URL, without the token nor the JSON Content-Type', async () => {
563
+ const spy = vi
564
+ .spyOn(globalThis, 'fetch')
565
+ .mockResolvedValue(new Response(null, { status: 200 }))
566
+ client.setAccessToken('token-1')
567
+ const response = await client.requestExternal('PUT', SIGNED_URL, 'a;b', {
568
+ 'Content-Type': 'text/csv',
569
+ })
570
+ expect(spy).toHaveBeenCalledWith(SIGNED_URL, {
571
+ method: 'PUT',
572
+ headers: { 'Content-Type': 'text/csv' },
573
+ body: 'a;b',
574
+ })
575
+ expect(response.status).toBe(200)
576
+ })
577
+
578
+ it('throws the ApiError subclass of a non-2xx answer, with the body as text', async () => {
579
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(
580
+ new Response('<Error><Code>AccessDenied</Code></Error>', {
581
+ status: 403,
582
+ statusText: 'Forbidden',
583
+ }),
584
+ )
585
+ const captured = await client.requestExternal('PUT', SIGNED_URL).catch((e) => e)
586
+ expect(captured).toBeInstanceOf(PermissionDeniedError)
587
+ expect((captured as ApiError).body).toBe('<Error><Code>AccessDenied</Code></Error>')
588
+ })
589
+
590
+ it('throws ApiConnectionError without the query string (the signature) in the message', async () => {
591
+ vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('fetch failed'))
592
+ const captured = await client.requestExternal('PUT', SIGNED_URL).catch((e) => e)
593
+ expect(captured).toBeInstanceOf(ApiConnectionError)
594
+ expect((captured as ApiConnectionError).message).toBe(
595
+ 'Request to PUT https://storage.example.com/acc-1/file-1.csv failed: fetch failed',
596
+ )
597
+ })
598
+ })
501
599
  })
@@ -1,6 +1,7 @@
1
1
  import type { ApiClient, HttpMethod } from './ApiClient'
2
2
  import { ApiConnectionError, ApiError } from './errors'
3
3
  import { DEFAULT_QUERY_FORMAT, type QueryFormat } from './queryString'
4
+ import { requestExternal } from './requestExternal'
4
5
 
5
6
  export interface BaseApiClientOptions {
6
7
  /** How list/get queries are serialized in the URL. Defaults to `'json'`. */
@@ -29,6 +30,53 @@ export class BaseApiClient implements ApiClient {
29
30
  body?: TBody,
30
31
  headers?: Record<string, string>,
31
32
  ): Promise<TResponse> {
33
+ return this.handleResponse<TResponse>(await this.send(method, endpoint, body, headers))
34
+ }
35
+
36
+ /**
37
+ * Sends the request and resolves the raw `Response` of a 2xx answer (headers and body
38
+ * untouched, so a binary body can be read with `arrayBuffer()`). Non-2xx throws the
39
+ * `ApiError` subclass, as {@link request} does.
40
+ */
41
+ async requestRaw<TBody = unknown>(
42
+ method: HttpMethod,
43
+ endpoint: string,
44
+ body?: TBody,
45
+ headers?: Record<string, string>,
46
+ ): Promise<Response> {
47
+ const response = await this.send(method, endpoint, body, headers)
48
+ if (!response.ok) {
49
+ throw ApiError.fromResponse(
50
+ response.status,
51
+ response.statusText,
52
+ response.headers,
53
+ await this.readErrorBody(response),
54
+ )
55
+ }
56
+ return response
57
+ }
58
+
59
+ /**
60
+ * Sends a request to an absolute URL outside the API, without the base URL, the access token
61
+ * and the JSON serialization. Resolves the raw `Response` of a 2xx answer; non-2xx throws the
62
+ * `ApiError` subclass. Override it to intercept those requests as well.
63
+ */
64
+ requestExternal(
65
+ method: HttpMethod,
66
+ url: string,
67
+ body?: BodyInit,
68
+ headers?: Record<string, string>,
69
+ ): Promise<Response> {
70
+ return requestExternal(method, url, body, headers)
71
+ }
72
+
73
+ /** Builds and sends the request; a network failure is thrown as `ApiConnectionError`. */
74
+ protected async send<TBody = unknown>(
75
+ method: HttpMethod,
76
+ endpoint: string,
77
+ body?: TBody,
78
+ headers?: Record<string, string>,
79
+ ): Promise<Response> {
32
80
  const url = `${this.baseUrl}/${endpoint.replace(/^\//, '')}`
33
81
  const init: RequestInit = {
34
82
  method,
@@ -40,17 +88,14 @@ export class BaseApiClient implements ApiClient {
40
88
  ...(body !== undefined && { body: JSON.stringify(body) }),
41
89
  }
42
90
 
43
- let response: Response
44
91
  try {
45
- response = await fetch(url, init)
92
+ return await fetch(url, init)
46
93
  } catch (error) {
47
94
  const reason = error instanceof Error ? error.message : String(error)
48
95
  throw new ApiConnectionError(`Request to ${method} ${url} failed: ${reason}`, {
49
96
  cause: error,
50
97
  })
51
98
  }
52
-
53
- return this.handleResponse<TResponse>(response)
54
99
  }
55
100
 
56
101
  protected async handleResponse<TResponse>(response: Response): Promise<TResponse> {
@@ -0,0 +1,43 @@
1
+ import type { HttpMethod } from './ApiClient'
2
+ import { ApiConnectionError, ApiError } from './errors'
3
+
4
+ /** The URL without its query string, which may carry a signature that must not reach the logs. */
5
+ function withoutQuery(url: string): string {
6
+ return url.replace(/[?#].*$/s, '')
7
+ }
8
+
9
+ /**
10
+ * Sends a request to an absolute URL outside the API (e.g. a signed storage URL): no base URL, no
11
+ * `Authorization` header and no JSON serialization, `body` goes as is. Resolves the raw `Response`
12
+ * of a 2xx answer; non-2xx throws the `ApiError` subclass with the body as text, and a network
13
+ * failure throws `ApiConnectionError`.
14
+ */
15
+ export async function requestExternal(
16
+ method: HttpMethod,
17
+ url: string,
18
+ body?: BodyInit,
19
+ headers?: Record<string, string>,
20
+ ): Promise<Response> {
21
+ let response: Response
22
+ try {
23
+ response = await fetch(url, {
24
+ method,
25
+ ...(headers && { headers }),
26
+ ...(body !== undefined && { body }),
27
+ })
28
+ } catch (error) {
29
+ const reason = error instanceof Error ? error.message : String(error)
30
+ throw new ApiConnectionError(`Request to ${method} ${withoutQuery(url)} failed: ${reason}`, {
31
+ cause: error,
32
+ })
33
+ }
34
+ if (!response.ok) {
35
+ throw ApiError.fromResponse(
36
+ response.status,
37
+ response.statusText,
38
+ response.headers,
39
+ (await response.text().catch(() => undefined)) || undefined,
40
+ )
41
+ }
42
+ return response
43
+ }