@communecter/cocolight-api-client 1.0.154 → 1.0.156

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 (331) hide show
  1. package/dist/1035.cocolight-api-client.browser.js +1 -1
  2. package/dist/1035.cocolight-api-client.cjs +1 -1
  3. package/dist/1483.cocolight-api-client.browser.js +1 -1
  4. package/dist/1483.cocolight-api-client.cjs +1 -1
  5. package/dist/1491.cocolight-api-client.browser.js +1 -1
  6. package/dist/1491.cocolight-api-client.cjs +1 -1
  7. package/dist/1494.cocolight-api-client.browser.js +1 -1
  8. package/dist/1494.cocolight-api-client.cjs +1 -1
  9. package/dist/1641.cocolight-api-client.browser.js +1 -1
  10. package/dist/1641.cocolight-api-client.cjs +1 -1
  11. package/dist/1678.cocolight-api-client.browser.js +1 -1
  12. package/dist/1678.cocolight-api-client.cjs +1 -1
  13. package/dist/2041.cocolight-api-client.browser.js +1 -1
  14. package/dist/2041.cocolight-api-client.cjs +1 -1
  15. package/dist/2302.cocolight-api-client.browser.js +1 -1
  16. package/dist/2302.cocolight-api-client.cjs +1 -1
  17. package/dist/2313.cocolight-api-client.browser.js +1 -1
  18. package/dist/2313.cocolight-api-client.cjs +1 -1
  19. package/dist/2379.cocolight-api-client.browser.js +1 -1
  20. package/dist/2379.cocolight-api-client.cjs +1 -1
  21. package/dist/2439.cocolight-api-client.browser.js +1 -1
  22. package/dist/2439.cocolight-api-client.cjs +1 -1
  23. package/dist/2594.cocolight-api-client.browser.js +1 -1
  24. package/dist/2594.cocolight-api-client.cjs +1 -1
  25. package/dist/2766.cocolight-api-client.browser.js +1 -1
  26. package/dist/2766.cocolight-api-client.cjs +1 -1
  27. package/dist/2812.cocolight-api-client.browser.js +1 -1
  28. package/dist/2812.cocolight-api-client.cjs +1 -1
  29. package/dist/2918.cocolight-api-client.browser.js +1 -1
  30. package/dist/2918.cocolight-api-client.cjs +1 -1
  31. package/dist/3028.cocolight-api-client.browser.js +1 -1
  32. package/dist/3028.cocolight-api-client.cjs +1 -1
  33. package/dist/3330.cocolight-api-client.browser.js +1 -1
  34. package/dist/3330.cocolight-api-client.cjs +1 -1
  35. package/dist/3371.cocolight-api-client.browser.js +1 -1
  36. package/dist/3371.cocolight-api-client.cjs +1 -1
  37. package/dist/3401.cocolight-api-client.browser.js +1 -1
  38. package/dist/3401.cocolight-api-client.cjs +1 -1
  39. package/dist/3516.cocolight-api-client.browser.js +1 -1
  40. package/dist/3516.cocolight-api-client.cjs +1 -1
  41. package/dist/3593.cocolight-api-client.browser.js +1 -1
  42. package/dist/3593.cocolight-api-client.cjs +1 -1
  43. package/dist/3764.cocolight-api-client.browser.js +1 -1
  44. package/dist/3764.cocolight-api-client.cjs +1 -1
  45. package/dist/3873.cocolight-api-client.browser.js +1 -1
  46. package/dist/3873.cocolight-api-client.cjs +1 -1
  47. package/dist/3911.cocolight-api-client.browser.js +1 -1
  48. package/dist/3911.cocolight-api-client.cjs +1 -1
  49. package/dist/4031.cocolight-api-client.browser.js +1 -1
  50. package/dist/4031.cocolight-api-client.cjs +1 -1
  51. package/dist/4152.cocolight-api-client.browser.js +1 -1
  52. package/dist/4152.cocolight-api-client.cjs +1 -1
  53. package/dist/4203.cocolight-api-client.browser.js +1 -1
  54. package/dist/4203.cocolight-api-client.cjs +1 -1
  55. package/dist/424.cocolight-api-client.browser.js +1 -1
  56. package/dist/424.cocolight-api-client.cjs +1 -1
  57. package/dist/435.cocolight-api-client.browser.js +1 -1
  58. package/dist/435.cocolight-api-client.cjs +1 -1
  59. package/dist/4402.cocolight-api-client.browser.js +1 -1
  60. package/dist/4402.cocolight-api-client.cjs +1 -1
  61. package/dist/4425.cocolight-api-client.browser.js +1 -1
  62. package/dist/4425.cocolight-api-client.cjs +1 -1
  63. package/dist/4472.cocolight-api-client.browser.js +1 -1
  64. package/dist/4472.cocolight-api-client.cjs +1 -1
  65. package/dist/4914.cocolight-api-client.browser.js +1 -1
  66. package/dist/4914.cocolight-api-client.cjs +1 -1
  67. package/dist/4959.cocolight-api-client.browser.js +1 -1
  68. package/dist/4959.cocolight-api-client.cjs +1 -1
  69. package/dist/5100.cocolight-api-client.browser.js +1 -1
  70. package/dist/5100.cocolight-api-client.cjs +1 -1
  71. package/dist/5111.cocolight-api-client.browser.js +1 -1
  72. package/dist/5111.cocolight-api-client.cjs +1 -1
  73. package/dist/5286.cocolight-api-client.browser.js +1 -1
  74. package/dist/5286.cocolight-api-client.cjs +1 -1
  75. package/dist/5380.cocolight-api-client.browser.js +1 -1
  76. package/dist/5380.cocolight-api-client.cjs +1 -1
  77. package/dist/5383.cocolight-api-client.browser.js +1 -1
  78. package/dist/5383.cocolight-api-client.cjs +1 -1
  79. package/dist/5480.cocolight-api-client.browser.js +1 -1
  80. package/dist/5480.cocolight-api-client.cjs +1 -1
  81. package/dist/5541.cocolight-api-client.browser.js +1 -1
  82. package/dist/5541.cocolight-api-client.cjs +1 -1
  83. package/dist/5839.cocolight-api-client.browser.js +1 -1
  84. package/dist/5839.cocolight-api-client.cjs +1 -1
  85. package/dist/5862.cocolight-api-client.browser.js +1 -1
  86. package/dist/5862.cocolight-api-client.cjs +1 -1
  87. package/dist/5869.cocolight-api-client.browser.js +1 -1
  88. package/dist/5869.cocolight-api-client.cjs +1 -1
  89. package/dist/588.cocolight-api-client.browser.js +1 -1
  90. package/dist/588.cocolight-api-client.cjs +1 -1
  91. package/dist/601.cocolight-api-client.browser.js +1 -1
  92. package/dist/601.cocolight-api-client.cjs +1 -1
  93. package/dist/6012.cocolight-api-client.browser.js +1 -1
  94. package/dist/6012.cocolight-api-client.cjs +1 -1
  95. package/dist/6200.cocolight-api-client.browser.js +1 -1
  96. package/dist/6200.cocolight-api-client.cjs +1 -1
  97. package/dist/6369.cocolight-api-client.browser.js +1 -1
  98. package/dist/6369.cocolight-api-client.cjs +1 -1
  99. package/dist/7009.cocolight-api-client.browser.js +1 -1
  100. package/dist/7009.cocolight-api-client.cjs +1 -1
  101. package/dist/725.cocolight-api-client.browser.js +1 -1
  102. package/dist/725.cocolight-api-client.cjs +1 -1
  103. package/dist/7320.cocolight-api-client.browser.js +1 -1
  104. package/dist/7339.cocolight-api-client.browser.js +1 -1
  105. package/dist/7339.cocolight-api-client.cjs +1 -1
  106. package/dist/7398.cocolight-api-client.browser.js +1 -1
  107. package/dist/7398.cocolight-api-client.cjs +1 -1
  108. package/dist/7888.cocolight-api-client.browser.js +1 -1
  109. package/dist/7888.cocolight-api-client.cjs +1 -1
  110. package/dist/7975.cocolight-api-client.browser.js +1 -1
  111. package/dist/7975.cocolight-api-client.cjs +1 -1
  112. package/dist/809.cocolight-api-client.browser.js +1 -1
  113. package/dist/809.cocolight-api-client.cjs +1 -1
  114. package/dist/8300.cocolight-api-client.browser.js +1 -1
  115. package/dist/8300.cocolight-api-client.cjs +1 -1
  116. package/dist/8317.cocolight-api-client.browser.js +1 -1
  117. package/dist/8317.cocolight-api-client.cjs +1 -1
  118. package/dist/8525.cocolight-api-client.browser.js +1 -1
  119. package/dist/8525.cocolight-api-client.cjs +1 -1
  120. package/dist/8894.cocolight-api-client.browser.js +1 -1
  121. package/dist/8894.cocolight-api-client.cjs +1 -1
  122. package/dist/9301.cocolight-api-client.browser.js +1 -1
  123. package/dist/9301.cocolight-api-client.cjs +1 -1
  124. package/dist/9438.cocolight-api-client.browser.js +1 -1
  125. package/dist/9438.cocolight-api-client.cjs +1 -1
  126. package/dist/9478.cocolight-api-client.browser.js +1 -1
  127. package/dist/9478.cocolight-api-client.cjs +1 -1
  128. package/dist/9568.cocolight-api-client.browser.js +1 -1
  129. package/dist/9568.cocolight-api-client.cjs +1 -1
  130. package/dist/cocolight-api-client.browser.js +6 -6
  131. package/dist/cocolight-api-client.cjs +1 -1
  132. package/dist/cocolight-api-client.vite.mjs.js +1 -1
  133. package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
  134. package/package.json +7 -17
  135. package/types/Api.d.ts +1 -0
  136. package/types/ApiClient.d.ts +10 -1
  137. package/types/api/BaseEntity.d.ts +24 -0
  138. package/types/api/EndpointApi.d.ts +4 -4
  139. package/types/api/EndpointApi.types.d.ts +3 -3
  140. package/types/utils/OfflineClientManager.d.ts +8 -0
  141. package/dist/1035.cocolight-api-client.mjs.js +0 -1
  142. package/dist/1483.cocolight-api-client.mjs.js +0 -1
  143. package/dist/1491.cocolight-api-client.mjs.js +0 -1
  144. package/dist/1494.cocolight-api-client.mjs.js +0 -1
  145. package/dist/1641.cocolight-api-client.mjs.js +0 -1
  146. package/dist/1678.cocolight-api-client.mjs.js +0 -1
  147. package/dist/2041.cocolight-api-client.mjs.js +0 -1
  148. package/dist/2302.cocolight-api-client.mjs.js +0 -1
  149. package/dist/2313.cocolight-api-client.mjs.js +0 -1
  150. package/dist/2379.cocolight-api-client.mjs.js +0 -1
  151. package/dist/2439.cocolight-api-client.mjs.js +0 -1
  152. package/dist/2594.cocolight-api-client.mjs.js +0 -1
  153. package/dist/2766.cocolight-api-client.mjs.js +0 -1
  154. package/dist/2812.cocolight-api-client.mjs.js +0 -1
  155. package/dist/2918.cocolight-api-client.mjs.js +0 -1
  156. package/dist/3028.cocolight-api-client.mjs.js +0 -1
  157. package/dist/3330.cocolight-api-client.mjs.js +0 -1
  158. package/dist/3371.cocolight-api-client.mjs.js +0 -1
  159. package/dist/3401.cocolight-api-client.mjs.js +0 -1
  160. package/dist/3516.cocolight-api-client.mjs.js +0 -1
  161. package/dist/3593.cocolight-api-client.mjs.js +0 -1
  162. package/dist/3764.cocolight-api-client.mjs.js +0 -1
  163. package/dist/3873.cocolight-api-client.mjs.js +0 -1
  164. package/dist/3911.cocolight-api-client.mjs.js +0 -1
  165. package/dist/4031.cocolight-api-client.mjs.js +0 -1
  166. package/dist/4152.cocolight-api-client.mjs.js +0 -1
  167. package/dist/4203.cocolight-api-client.mjs.js +0 -1
  168. package/dist/424.cocolight-api-client.mjs.js +0 -1
  169. package/dist/435.cocolight-api-client.mjs.js +0 -1
  170. package/dist/4402.cocolight-api-client.mjs.js +0 -1
  171. package/dist/4425.cocolight-api-client.mjs.js +0 -1
  172. package/dist/4472.cocolight-api-client.mjs.js +0 -1
  173. package/dist/4914.cocolight-api-client.mjs.js +0 -1
  174. package/dist/4959.cocolight-api-client.mjs.js +0 -1
  175. package/dist/5100.cocolight-api-client.mjs.js +0 -1
  176. package/dist/5111.cocolight-api-client.mjs.js +0 -1
  177. package/dist/5286.cocolight-api-client.mjs.js +0 -1
  178. package/dist/5380.cocolight-api-client.mjs.js +0 -1
  179. package/dist/5383.cocolight-api-client.mjs.js +0 -1
  180. package/dist/5480.cocolight-api-client.mjs.js +0 -1
  181. package/dist/5541.cocolight-api-client.mjs.js +0 -1
  182. package/dist/5839.cocolight-api-client.mjs.js +0 -1
  183. package/dist/5862.cocolight-api-client.mjs.js +0 -1
  184. package/dist/5869.cocolight-api-client.mjs.js +0 -1
  185. package/dist/588.cocolight-api-client.mjs.js +0 -1
  186. package/dist/601.cocolight-api-client.mjs.js +0 -1
  187. package/dist/6012.cocolight-api-client.mjs.js +0 -1
  188. package/dist/6200.cocolight-api-client.mjs.js +0 -1
  189. package/dist/6369.cocolight-api-client.mjs.js +0 -1
  190. package/dist/7009.cocolight-api-client.mjs.js +0 -1
  191. package/dist/725.cocolight-api-client.mjs.js +0 -1
  192. package/dist/7339.cocolight-api-client.mjs.js +0 -1
  193. package/dist/7398.cocolight-api-client.mjs.js +0 -1
  194. package/dist/7888.cocolight-api-client.mjs.js +0 -1
  195. package/dist/7975.cocolight-api-client.mjs.js +0 -1
  196. package/dist/809.cocolight-api-client.mjs.js +0 -1
  197. package/dist/8300.cocolight-api-client.mjs.js +0 -1
  198. package/dist/8317.cocolight-api-client.mjs.js +0 -1
  199. package/dist/8525.cocolight-api-client.mjs.js +0 -1
  200. package/dist/8894.cocolight-api-client.mjs.js +0 -1
  201. package/dist/9301.cocolight-api-client.mjs.js +0 -1
  202. package/dist/9438.cocolight-api-client.mjs.js +0 -1
  203. package/dist/9478.cocolight-api-client.mjs.js +0 -1
  204. package/dist/9568.cocolight-api-client.mjs.js +0 -1
  205. package/dist/cocolight-api-client.mjs.js +0 -1
  206. package/src/Api.ts +0 -348
  207. package/src/ApiClient.ts +0 -1994
  208. package/src/EJSONType.ts +0 -103
  209. package/src/api/Action.ts +0 -665
  210. package/src/api/Answer.ts +0 -1392
  211. package/src/api/Badge.ts +0 -143
  212. package/src/api/BaseEntity.ts +0 -6338
  213. package/src/api/Classified.ts +0 -175
  214. package/src/api/Comment.ts +0 -280
  215. package/src/api/EndpointApi.ts +0 -2270
  216. package/src/api/EndpointApi.types.ts +0 -6566
  217. package/src/api/EntityRegistry.ts +0 -324
  218. package/src/api/Event.ts +0 -360
  219. package/src/api/Form.ts +0 -282
  220. package/src/api/News.ts +0 -547
  221. package/src/api/Notifications.ts +0 -196
  222. package/src/api/Organization.ts +0 -771
  223. package/src/api/Poi.ts +0 -242
  224. package/src/api/Project.ts +0 -545
  225. package/src/api/User.ts +0 -1889
  226. package/src/api/UserApi.ts +0 -192
  227. package/src/api/serverDataType/Action.ts +0 -177
  228. package/src/api/serverDataType/Answer.ts +0 -106
  229. package/src/api/serverDataType/Classified.ts +0 -152
  230. package/src/api/serverDataType/Comment.ts +0 -88
  231. package/src/api/serverDataType/Country.ts +0 -1
  232. package/src/api/serverDataType/Event.ts +0 -112
  233. package/src/api/serverDataType/Form.ts +0 -157
  234. package/src/api/serverDataType/News.ts +0 -138
  235. package/src/api/serverDataType/Notification.ts +0 -81
  236. package/src/api/serverDataType/Organization.ts +0 -111
  237. package/src/api/serverDataType/Poi.ts +0 -75
  238. package/src/api/serverDataType/Project.ts +0 -115
  239. package/src/api/serverDataType/User.ts +0 -112
  240. package/src/api/serverDataType/Zone.ts +0 -33
  241. package/src/api/serverDataType/common.ts +0 -84
  242. package/src/costum/normalize.ts +0 -7
  243. package/src/costum/registry.ts +0 -138
  244. package/src/costum/runtime-map.ts +0 -221
  245. package/src/costum/runtime.ts +0 -274
  246. package/src/costum/schemas/CercleTravailleursSociauxLiberauxLaPlumeSociale.ts +0 -19
  247. package/src/costum/schemas/FabEn.ts +0 -19
  248. package/src/costum/schemas/LivingLabInnovationSocialeBraspanon.ts +0 -19
  249. package/src/costum/schemas/alfph.ts +0 -27
  250. package/src/costum/schemas/campagne.ts +0 -19
  251. package/src/costum/schemas/chtitube.ts +0 -19
  252. package/src/costum/schemas/circuitCourt.ts +0 -19
  253. package/src/costum/schemas/cmcasHauteBretagne.ts +0 -19
  254. package/src/costum/schemas/coFinancementTierslieux.ts +0 -27
  255. package/src/costum/schemas/coeurNumerique.ts +0 -19
  256. package/src/costum/schemas/communLundi.ts +0 -19
  257. package/src/costum/schemas/cressReunion.ts +0 -35
  258. package/src/costum/schemas/cressReunionNetwork.ts +0 -35
  259. package/src/costum/schemas/ctenat.ts +0 -43
  260. package/src/costum/schemas/cyberReunion.ts +0 -19
  261. package/src/costum/schemas/ekisphere.ts +0 -35
  262. package/src/costum/schemas/equipementsSportifs974.ts +0 -19
  263. package/src/costum/schemas/federationDesCae.ts +0 -19
  264. package/src/costum/schemas/formationGenerique.ts +0 -35
  265. package/src/costum/schemas/franceTierslieux.ts +0 -19
  266. package/src/costum/schemas/hinaura.ts +0 -27
  267. package/src/costum/schemas/hubmayotte.ts +0 -19
  268. package/src/costum/schemas/hubnouvellecaledonie.ts +0 -19
  269. package/src/costum/schemas/hubpolynesiefrancaise.ts +0 -19
  270. package/src/costum/schemas/hubreunion.ts +0 -19
  271. package/src/costum/schemas/hubterritoires.ts +0 -19
  272. package/src/costum/schemas/hubwallisetfutuna.ts +0 -19
  273. package/src/costum/schemas/hva.ts +0 -27
  274. package/src/costum/schemas/ibv2.ts +0 -19
  275. package/src/costum/schemas/institutBleu.ts +0 -19
  276. package/src/costum/schemas/journalInsoumisChambery.ts +0 -27
  277. package/src/costum/schemas/kpacite.ts +0 -19
  278. package/src/costum/schemas/laCompagnieDesTierslieux.ts +0 -43
  279. package/src/costum/schemas/laMaisonDesFaiseurs.ts +0 -19
  280. package/src/costum/schemas/laReunionPourUnNumeriqueInclusif.ts +0 -19
  281. package/src/costum/schemas/laSemaineDesTierslieux.ts +0 -19
  282. package/src/costum/schemas/labEnCommuns.ts +0 -19
  283. package/src/costum/schemas/lakou.ts +0 -27
  284. package/src/costum/schemas/lesCommunsDesTierslieux.ts +0 -19
  285. package/src/costum/schemas/mayottePourUnNumeriqueInclusif.ts +0 -19
  286. package/src/costum/schemas/meir.ts +0 -27
  287. package/src/costum/schemas/navigatorDesTierslieux.ts +0 -19
  288. package/src/costum/schemas/notragora.ts +0 -27
  289. package/src/costum/schemas/nouvellecaledoniePourUnNumeriqueInclusif.ts +0 -19
  290. package/src/costum/schemas/openAtlas.ts +0 -19
  291. package/src/costum/schemas/polynesieFrancaisePourUnNumeriqueInclusif.ts +0 -19
  292. package/src/costum/schemas/prdr.ts +0 -19
  293. package/src/costum/schemas/reseauTierslieux.ts +0 -19
  294. package/src/costum/schemas/rfflabs.ts +0 -19
  295. package/src/costum/schemas/ries.ts +0 -19
  296. package/src/costum/schemas/siteDuPactePourLaTransition.ts +0 -27
  297. package/src/costum/schemas/sportSanteBienetre.ts +0 -43
  298. package/src/costum/schemas/suppa.ts +0 -19
  299. package/src/costum/schemas/terramies.ts +0 -19
  300. package/src/costum/schemas/testeTplFormation.ts +0 -35
  301. package/src/costum/schemas/tierslieuxbelgique.ts +0 -19
  302. package/src/costum/schemas/tierslieuxorg1.ts +0 -35
  303. package/src/costum/schemas/transiter.ts +0 -35
  304. package/src/costum/schemas/vhelioPeiTour2025.ts +0 -19
  305. package/src/costum/schemas/wallisetfutunaPourUnNumeriqueInclusif.ts +0 -19
  306. package/src/costum/symbol.ts +0 -26
  307. package/src/costum/types.ts +0 -895
  308. package/src/endpoints.module.ts +0 -4253
  309. package/src/error.ts +0 -86
  310. package/src/forms/entityForm.ts +0 -142
  311. package/src/index.ts +0 -161
  312. package/src/mixin/UserMixin.ts +0 -4
  313. package/src/types/api-responses.ts +0 -217
  314. package/src/types/entities.ts +0 -58
  315. package/src/types/error-guards.ts +0 -230
  316. package/src/types/index.ts +0 -41
  317. package/src/types/payloads.ts +0 -21
  318. package/src/types/transforms.ts +0 -119
  319. package/src/utils/FileOfflineStorageStrategy.node.ts +0 -50
  320. package/src/utils/FileStorageStrategy.node.ts +0 -63
  321. package/src/utils/MultiServerFileStorageStrategy.node.ts +0 -67
  322. package/src/utils/MultiServerTokenStorageStrategy.ts +0 -139
  323. package/src/utils/OfflineClientManager.ts +0 -216
  324. package/src/utils/OfflineQueueStorageStrategy.ts +0 -47
  325. package/src/utils/TokenStorage.ts +0 -77
  326. package/src/utils/compat.ts +0 -12
  327. package/src/utils/createDefaultMultiServerTokenStorageStrategy.ts +0 -35
  328. package/src/utils/createDefaultOfflineStrategy.ts +0 -34
  329. package/src/utils/createDefaultTokenStorageStrategy.ts +0 -33
  330. package/src/utils/reactive.ts +0 -243
  331. package/src/utils/stream-utils.node.ts +0 -12
package/src/ApiClient.ts DELETED
@@ -1,1994 +0,0 @@
1
- import { EventEmitter } from "events";
2
-
3
- import { AggregateAjvError } from "@segment/ajv-human-errors";
4
- import axios from "axios";
5
- import axiosRetryImport from "axios-retry";
6
- import { jwtDecode } from "jwt-decode";
7
-
8
-
9
- import { COSTUM_NUMBER_FIELDS, COSTUM_DATE_FIELDS } from "./costum/normalize.js";
10
- import { COSTUM_REQUEST_ENVELOPE, type CostumRequestEnvelope } from "./costum/symbol.js";
11
- import MongoID from "./EJSONType.js";
12
- import endpointsJson from "./endpoints.module.js";
13
- import { ApiClientError, ApiResponseError, ApiValidationError, CircuitBreakerError } from "./error.js";
14
- import { getErrorMessage, getErrorStatusCode, hasResponse } from "./types/error-guards.js";
15
- import { Ajv, addFormats, pino, EJSON } from "./utils/compat.js";
16
- import { MultiServerTokenStorageStrategy } from "./utils/MultiServerTokenStorageStrategy.js";
17
- import { MemoryStorageStrategy, type TokenStorageStrategy } from "./utils/TokenStorage.js";
18
-
19
- import type { ApiErrorDetails } from "./types/api-responses.js";
20
- import type OfflineClientManager from "./utils/OfflineClientManager.js";
21
- import type { ValidateFunction } from "ajv";
22
- import type { AxiosInstance } from "axios";
23
- import type { JwtPayload } from "jwt-decode";
24
-
25
- // Fix pour le typage de axios-retry avec les modules CommonJS
26
- interface AxiosRetryType {
27
- (axiosInstance: AxiosInstance, config?: any): void;
28
- exponentialDelay: (retryNumber?: number, error?: Error, delayFactor?: number) => number;
29
- isNetworkError: (error: Error) => boolean;
30
- isRetryableError: (error: Error) => boolean;
31
- }
32
- const axiosRetry = axiosRetryImport as unknown as AxiosRetryType;
33
-
34
- /**
35
- * Résultat retourné quand une requête est **mise en file** (offline ou breaker).
36
- * Permet de typer précisément les chemins « non-réseau » de `callEndpoint`.
37
- */
38
- export interface ApiClientOfflineEnqueueResult {
39
- data: null;
40
- offline?: true; // Présent si l'enqueue est dû au mode offline.
41
- breaker?: true; // Présent si l'enqueue est dû au circuit breaker.
42
- }
43
-
44
- /**
45
- * Options de configuration pour ApiClient
46
- */
47
- export interface ApiClientOptions {
48
- baseURL: string;
49
- accessToken?: string | null;
50
- refreshToken?: string | null;
51
- refreshUrl?: string;
52
- endpoints?: any[];
53
- timeout?: number;
54
- debug?: boolean;
55
- maxRetries?: number;
56
- circuitBreakerThreshold?: number;
57
- circuitBreakerResetTime?: number;
58
- fromJSONValue?: boolean;
59
- /**
60
- * Mode de dialogue avec le backend cocolight-backend :
61
- * - `legacy` (défaut) : réponses byte-compatibles PHP ; la lib applique TOUTES ses
62
- * normalisations côté client (_transformData/_normalizeJsonData) + validation AJV.
63
- * - `clean` : envoie `X-Coco-Mode: clean` ; le SERVEUR applique les normalisations et
64
- * répond {success, data|error}. La lib unwrap l'enveloppe, lève ApiResponseError sur
65
- * success:false, SAUTE _transformData et la validation AJV (schémas legacy), et revive
66
- * (dates ISO -> Date, id -> _id ObjectID) pour rendre à l'app le MÊME shape qu'avant.
67
- * NB : l'intercepteur refresh-token reste en legacy (appel interne sans le header).
68
- */
69
- mode?: "legacy" | "clean";
70
- tokenStorageStrategy?: TokenStorageStrategy | null;
71
- }
72
-
73
- /**
74
- * Structure des données pour les appels d'endpoint
75
- */
76
- export interface CallEndpointData {
77
- pathParams?: Record<string, any>;
78
- [key: string]: any;
79
- }
80
-
81
- /**
82
- * Résultat d'un appel d'endpoint réussi
83
- */
84
- export interface CallEndpointResult<T = any> {
85
- data: T;
86
- offline?: never;
87
- breaker?: never;
88
- }
89
-
90
- /**
91
- * Union type pour tous les résultats possibles de callEndpoint
92
- */
93
- export type CallEndpointResponse<T = any> = CallEndpointResult<T> | ApiClientOfflineEnqueueResult;
94
-
95
- EJSON.addType("oid", (value: any) => {
96
- if (typeof value === "string") {
97
- return new MongoID.ObjectID(value);
98
- }
99
- if (typeof value === "undefined") {
100
- return new MongoID.ObjectID();
101
- }
102
- throw new Error("Expected string value for ObjectID");
103
- });
104
-
105
-
106
- /**
107
- * Client générique pour consommer une API REST avec validation AJV, gestion des tokens,
108
- * circuit breaker, retry automatique, et support offline.
109
- *
110
- * @fires ApiClient#retryAttempt
111
- * @fires ApiClient#queuedOffline
112
- * @fires ApiClient#circuitBreakerOpen
113
- * @fires ApiClient#circuitBreakerReset
114
- * @fires ApiClient#refreshSuccess
115
- * @fires ApiClient#refreshFailed
116
- * @fires ApiClient#sessionReset
117
- * @fires ApiClient#validationError
118
- * @fires ApiClient#offlineModeChanged
119
- * @fires ApiClient#userLoggedIn
120
- */
121
- export default class ApiClient extends EventEmitter {
122
- // Public properties
123
- public readonly __entityTag: string = "ApiClient";
124
- public readonly userId: string | null = null;
125
-
126
- // Private properties
127
- private readonly _baseURL: string;
128
- private readonly _refreshUrl: string;
129
- private readonly _endpoints: any[];
130
- private readonly _debug: boolean;
131
- private readonly _fromJSONValue: boolean;
132
- private readonly _tokenStorage: TokenStorageStrategy;
133
-
134
- private _offlineClientManager: OfflineClientManager | null = null;
135
- private _ajv: any;
136
- private _ajvNoDefaults: any;
137
- public _logger: any;
138
- private _client: AxiosInstance;
139
-
140
- // Circuit breaker properties
141
- private readonly _breakerThreshold: number;
142
- private readonly _breakerResetTime: number;
143
- private _breakerErrorCount: number = 0;
144
- private _breakerOpen: boolean = false;
145
- private _lastBreakerOpenTime: number | null = null;
146
-
147
- // Token properties
148
- private _accessToken: string | null = null;
149
- private _refreshToken: string | null = null;
150
-
151
- // Internal userId setter
152
- private _setUserId: (id: string | null) => void;
153
- private _mode: "legacy" | "clean" = "legacy";
154
- constructor({
155
- baseURL,
156
- accessToken,
157
- refreshToken,
158
- refreshUrl = "/api/cocolight/refreshtoken",
159
- endpoints = endpointsJson.endpoints,
160
- timeout = 30000,
161
- debug = false,
162
- maxRetries = 0,
163
- circuitBreakerThreshold = 5,
164
- circuitBreakerResetTime = 60000,
165
- fromJSONValue = true,
166
- mode = "legacy",
167
- tokenStorageStrategy = null
168
- }: ApiClientOptions) {
169
- super(); // EventEmitter
170
-
171
- if (!baseURL) {
172
- throw new ApiClientError("Le paramètre \"baseURL\" est obligatoire.", 500);
173
- }
174
-
175
- this._baseURL = baseURL;
176
- this._refreshUrl = refreshUrl;
177
- this._endpoints = endpoints || endpointsJson.endpoints;
178
- this._debug = debug;
179
- this._fromJSONValue = fromJSONValue;
180
- this._mode = mode === "clean" ? "clean" : "legacy";
181
- this._breakerThreshold = circuitBreakerThreshold;
182
- this._breakerResetTime = circuitBreakerResetTime;
183
-
184
- // Setup userId with getter/setter pattern
185
- let _userId: string | null = null;
186
-
187
- Object.defineProperty(this, "userId", {
188
- get: () => _userId,
189
- set: () => {
190
- throw new Error("Modification directe de userId non autorisée.");
191
- },
192
- enumerable: true
193
- });
194
-
195
- this._setUserId = (id: string | null) => {
196
- _userId = id;
197
- this._logger?.debug(`[ApiClient] userId set: ${id}`);
198
- };
199
-
200
- // AJV
201
- this._ajv = new Ajv({ strict: false, useDefaults: true, allErrors: true, verbose: true });
202
- // Variante SANS injection de défauts : utilisée pour l'UPDATE costum via `element/save`
203
- // (enveloppe.isUpdate) — on n'envoie que les champs modifiés, jamais les défauts de création.
204
- this._ajvNoDefaults = new Ajv({ strict: false, useDefaults: false, allErrors: true, verbose: true });
205
- for (const ajv of [this._ajv, this._ajvNoDefaults]) {
206
- addFormats(ajv);
207
- ajv.addFormat("textarea", true);
208
- ajv.addKeyword({
209
- keyword: "startBeforeEnd",
210
- type: "object",
211
- errors: true,
212
- validate: this._startBeforeEndValidate
213
- });
214
- }
215
-
216
- // Pino logger
217
- this._logger = pino({
218
- transport: {
219
- target: "pino-pretty",
220
- options: { colorize: true }
221
- },
222
- level: debug ? "debug" : "info"
223
- });
224
-
225
- // Création instance Axios
226
- this._client = axios.create({
227
- baseURL,
228
- timeout
229
- });
230
- // axios-retry : pour retenter en cas de soucis réseau ou code 5xx
231
- if (maxRetries > 0) {
232
- axiosRetry(this._client, {
233
- retries: maxRetries,
234
- retryDelay: axiosRetry.exponentialDelay,
235
- retryCondition: (error: any) => {
236
- // Retry sur erreurs 5xx ou erreurs réseau
237
- return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error);
238
- },
239
- onRetry: (retryCount: number, error: any, requestConfig: any) => {
240
- this._logger.warn(`[Retry] Tentative #${retryCount} pour ${requestConfig?.url}`);
241
- this.emit("retryAttempt", { retryCount, url: requestConfig?.url });
242
- }
243
- });
244
- this._logger.info(`[ApiClient] Retry activé : ${maxRetries} max`);
245
- }
246
-
247
- if (tokenStorageStrategy instanceof MultiServerTokenStorageStrategy) {
248
- tokenStorageStrategy.use(this._baseURL);
249
- }
250
-
251
- this._tokenStorage = tokenStorageStrategy || new MemoryStorageStrategy();
252
-
253
- if (
254
- typeof this._tokenStorage?.getAccessToken !== "function" ||
255
- typeof this._tokenStorage?.setAccessToken !== "function" ||
256
- typeof this._tokenStorage?.getRefreshToken !== "function" ||
257
- typeof this._tokenStorage?.setRefreshToken !== "function" ||
258
- typeof this._tokenStorage?.clear !== "function"
259
- ) {
260
- throw new Error("[ApiClient] La stratégie de stockage des tokens n’est pas valide. Elle doit implémenter les méthodes requises.");
261
- }
262
-
263
- // Applique un token initial s'il est fourni
264
- if (refreshToken) {
265
- this.setRefreshToken(refreshToken);
266
- }
267
-
268
- if (accessToken) {
269
- this.setToken(accessToken);
270
- }
271
-
272
- const initialAccessToken = this._tokenStorage.getAccessToken();
273
- if (initialAccessToken) {
274
- this.setToken(initialAccessToken);
275
- }
276
-
277
- const initialRefreshToken = this._tokenStorage.getRefreshToken();
278
- if (initialRefreshToken) {
279
- this.setRefreshToken(initialRefreshToken);
280
- }
281
-
282
-
283
- // Intercepteur 401 -> refresh
284
- this._client.interceptors.response.use(
285
- response => response,
286
- async error => {
287
- const originalRequest = error.config;
288
-
289
- // Si la requête est déjà réessayée, abandonne
290
- if (originalRequest._retry) {
291
- this._logger.error("[ApiClient] Requête déjà retentée, échec définitif.");
292
- throw error;
293
- }
294
-
295
- if (error.response && error.response.status === 401 && this._refreshToken) {
296
- originalRequest._retry = true;
297
-
298
- try {
299
- this._logger.info("[ApiClient] Tentative de refresh du token...");
300
- const refreshed = await this._refreshAccessToken();
301
-
302
- if (refreshed) {
303
- this._logger.info("[ApiClient] Token rafraîchi avec succès.");
304
-
305
- // 🔑 Mise à jour EXPLICITE du header Authorization dans la requête originale
306
- originalRequest.headers = originalRequest.headers ?? {};
307
- originalRequest.headers["Authorization"] = "Bearer " + this.getToken();
308
-
309
- this._logger.info("[ApiClient] Retente la requête originale avec le nouveau token.");
310
- return this._client.request(originalRequest);
311
- } else {
312
- this.resetSession();
313
- throw new ApiClientError("Impossible de rafraîchir le token.", 401);
314
- }
315
- } catch (err) {
316
- this.resetSession();
317
- const errorDetails: ApiErrorDetails = {
318
- code: getErrorStatusCode(err) || 401,
319
- message: getErrorMessage(err)
320
- };
321
- throw new ApiClientError("Erreur lors du rafraîchissement du token.", 401, errorDetails);
322
- }
323
- }
324
-
325
- throw error;
326
- }
327
- );
328
-
329
- }
330
-
331
- /**
332
- * Sets the access token for the API client and updates the authorization header.
333
- */
334
- setToken(token: string | null): void {
335
- this._accessToken = token;
336
- this._tokenStorage.setAccessToken(token ?? "");
337
- if (token) {
338
- this._client.defaults.headers.common["Authorization"] = "Bearer " + token;
339
- } else {
340
- delete this._client.defaults.headers.common["Authorization"];
341
- }
342
- // Extrait l'id depuis le token et le stocke si disponible
343
- const userId = this._getIdFromToken(token);
344
- if (userId) {
345
- this._setUserId(userId);
346
- this._logger.debug(`[ApiClient] userId extrait et défini : ${userId}`);
347
- }
348
- this._logger.debug(`[ApiClient] setToken: ${token}`);
349
- }
350
-
351
- /**
352
- * Retrieves the current access token.
353
- */
354
- getToken(): string | null {
355
- return this._accessToken;
356
- }
357
-
358
- /**
359
- * Sets the refresh token for the API client.
360
- */
361
- setRefreshToken(token: string | null): void {
362
- this._refreshToken = token;
363
- this._tokenStorage.setRefreshToken(token ?? "");
364
- if(this.userId === null){
365
- // Extrait l'id depuis le token et le stocke si disponible
366
- const userId = this._getIdFromToken(token);
367
- if (userId) {
368
- this._setUserId(userId);
369
- this._logger.debug(`[ApiClient] userId extrait depuis refreshToken : ${userId}`);
370
- }
371
- }
372
- this._logger.debug(`[ApiClient] setRefreshToken: ${token}`);
373
- }
374
-
375
- /**
376
- * Retrieves the current refresh token.
377
- */
378
- getRefreshToken(): string | null {
379
- return this._refreshToken;
380
- }
381
-
382
- /**
383
- * Indique si le client est connecté.
384
- * On considère que le client est connecté si un token d'accès (_accessToken) est défini.
385
- */
386
- get isConnected(): boolean {
387
- return !!this._accessToken;
388
- }
389
-
390
- /**
391
- * Extrait l'identifiant depuis un JWT.
392
- */
393
- private _getIdFromToken(token: string | null): string | null {
394
- if (!token) return null;
395
- try {
396
- // Décodage du token grâce à jwt-decode
397
- const payload = jwtDecode<JwtPayload & { id?: string; userId?: string }>(token);
398
- // L'identifiant peut être dans "id" ou "userId"
399
- this._logger.debug("[ApiClient] Payload décodé :", payload);
400
- return payload.id || payload.userId || null;
401
- } catch (err) {
402
- this._logger.error("[ApiClient] Erreur lors du décodage du token :", err);
403
- return null;
404
- }
405
- }
406
-
407
- /**
408
- * Méthode simplifiée de refresh (en JSON).
409
- * Emet un event refreshSuccess si ça marche
410
- */
411
- private async _refreshAccessToken(): Promise<boolean> {
412
- if (!this._refreshToken) return false;
413
-
414
- const refreshClient = axios.create({
415
- baseURL: this._baseURL,
416
- timeout: 10000, // ajuster si nécessaire
417
- headers: { "Content-Type": "application/json" }
418
- });
419
-
420
- try {
421
- const response = await refreshClient.post(this._refreshUrl, {
422
- refreshToken: this._refreshToken
423
- });
424
- if (response.data && response.data.token) {
425
- this.setToken(response.data.token);
426
- if (response.data.refreshToken) {
427
- this.setRefreshToken(response.data.refreshToken);
428
- }
429
- this.emit("refreshSuccess", response.data);
430
- return true;
431
- }
432
- return false;
433
- } catch (err) {
434
- // Si on a une erreur, on reset la session
435
- this.emit("refreshFailed", { error: getErrorMessage(err) });
436
- this.resetSession();
437
- this._logger.error(`[ApiClient] Refresh Error : ${getErrorMessage(err)}`);
438
- return false;
439
- }
440
- }
441
-
442
- /**
443
- * checkCircuitBreaker : vérifie si on peut appeler l'API ou non
444
- * si le breaker est "open", on regarde si on peut "reset"
445
- */
446
- private _checkCircuitBreaker(): boolean {
447
- if (!this._breakerOpen) return true;
448
- const now = Date.now();
449
- if (this._lastBreakerOpenTime != null && (now - this._lastBreakerOpenTime) > this._breakerResetTime) {
450
- this._breakerOpen = false;
451
- this._breakerErrorCount = 0;
452
- this._logger.warn("[ApiClient] Circuit breaker réinitialisé");
453
- this.emit("circuitBreakerReset");
454
- return true;
455
- }
456
- return false;
457
- }
458
-
459
- /**
460
- * updateCircuitBreaker : incremente le compteur d'erreurs
461
- * si on dépasse le threshold, on "open" le breaker.
462
- */
463
- private _updateCircuitBreakerError(): void {
464
- this._breakerErrorCount += 1;
465
- this._logger.warn(`[ApiClient] Erreur #${this._breakerErrorCount} sur ${this._breakerThreshold}`);
466
- if (this._breakerErrorCount >= this._breakerThreshold) {
467
- this._breakerOpen = true;
468
- this._lastBreakerOpenTime = Date.now();
469
- this._logger.error("[ApiClient] Circuit breaker ACTIVÉ - L'API est considérée indisponible");
470
- this.emit("circuitBreakerOpen", { timestamp: this._lastBreakerOpenTime });
471
- }
472
- }
473
-
474
- /**
475
- * resetCircuitBreaker : en cas de succès on reset le compteur
476
- */
477
- private _resetCircuitBreakerSuccess(): void {
478
- this._breakerErrorCount = 0;
479
- if (this._breakerOpen) {
480
- this._breakerOpen = false;
481
- this._logger.warn("[ApiClient] Circuit breaker refermé suite à un succès");
482
- }
483
- }
484
-
485
- static stripNullsInPlace(obj: any): any {
486
- // Si l'objet semble être un "fichier uploadé", c'est-à-dire qu'il possède une propriété "value" qui est un stream, on le laisse intact.
487
- if (obj && typeof obj === "object" && obj.value && typeof obj.value.pipe === "function") {
488
- return obj;
489
- }
490
-
491
- if (Array.isArray(obj)) {
492
- for (let i = obj.length - 1; i >= 0; i--) {
493
- if (obj[i] === null || obj[i] === undefined) {
494
- obj.splice(i, 1);
495
- } else if (typeof obj[i] === "object") {
496
- ApiClient.stripNullsInPlace(obj[i]);
497
- }
498
- }
499
- } else if (obj && typeof obj === "object") {
500
- for (const key in obj) {
501
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
502
- if (obj[key] === null || obj[key] === undefined) {
503
- delete obj[key];
504
- } else if (typeof obj[key] === "object") {
505
- ApiClient.stripNullsInPlace(obj[key]);
506
- }
507
- }
508
- }
509
- }
510
- return obj;
511
- }
512
-
513
- private _resolveSpecialValuesInPlace(obj: any, pathParams: any = {}) {
514
- const aliasMap: any = {
515
- userId: () => this.userId,
516
- accessToken: () => this._accessToken,
517
- refreshToken: () => this._refreshToken,
518
- baseURL: () => this._baseURL,
519
- pathParams: (subPath: any) => {
520
- // Si la valeur existe dans pathParams, on la retourne
521
- const value = this._getValueByPath(pathParams, subPath);
522
- return value !== undefined && value !== null ? value : undefined;
523
- }
524
- };
525
-
526
- // Expression régulière qui capture les alias sous forme délimitée @{...} ou simple @...
527
- const regex = /@(?:\{([^}]+)\}|([\w.]+))/g;
528
-
529
- const resolveString = (str: any) => {
530
- if (typeof str !== "string") return str;
531
- return str.replace(regex, (_, delimitedAlias, plainAlias) => {
532
- // Si alias délimité, on traite toute la chaîne à l'intérieur des accolades.
533
- if (delimitedAlias) {
534
- // On s'attend à une forme comme "pathParams.id.autre"
535
- if (delimitedAlias.startsWith("pathParams.")) {
536
- const subPath = delimitedAlias.substring("pathParams.".length);
537
- const value = aliasMap.pathParams(subPath);
538
- return value !== undefined && value !== null ? value : `@{${delimitedAlias}}`;
539
- } else {
540
- // Pour d'autres alias délimités, on cherche directement dans aliasMap
541
- const fn = aliasMap[delimitedAlias];
542
- if (typeof fn === "function") {
543
- const value = fn();
544
- return value !== undefined && value !== null ? value : `@{${delimitedAlias}}`;
545
- }
546
- return `@{${delimitedAlias}}`;
547
- }
548
- } else if (plainAlias) {
549
- // Traitement de l'alias simple, potentiellement avec des segments
550
- // Vérifier s'il commence par "pathParams."
551
- if (plainAlias.startsWith("pathParams.")) {
552
- // Extrait la partie après "pathParams." et sépare le premier segment et le reste.
553
- const match = plainAlias.match(/^pathParams\.([^.]+)(.*)$/);
554
- if (match) {
555
- const subKey = match[1]; // ex: "id"
556
- const remainder = match[2]; // ex: "" ou ".autrechose"
557
- // Si le reste est vide, ou s'il ne doit pas être remplacé (on remplace toujours le premier segment uniquement)
558
- const value = aliasMap.pathParams(subKey);
559
- return value !== undefined && value !== null ? value + remainder : `@${plainAlias}`;
560
- }
561
- return `@${plainAlias}`;
562
- } else {
563
- // Pour tout autre alias simple sans point ou autre
564
- const fn = aliasMap[plainAlias];
565
- if (typeof fn === "function") {
566
- const value = fn();
567
- return value !== undefined && value !== null ? value : `@${plainAlias}`;
568
- }
569
- return `@${plainAlias}`;
570
- }
571
- }
572
- return str;
573
- });
574
- };
575
-
576
-
577
- // Vérifie si l'objet est un binaire (par exemple un flux ou un Buffer)
578
- const isBinary = (input: any) => {
579
- return input && typeof input === "object" &&
580
- (typeof input.pipe === "function" || (typeof Buffer !== "undefined" && input instanceof Buffer));
581
- };
582
-
583
- // Vérifie si l'objet est un "plain object"
584
- const isPlainObject = (input: any) => {
585
- return Object.prototype.toString.call(input) === "[object Object]";
586
- };
587
-
588
- const internal = (input: any): any => {
589
- // Si c'est un binaire, ne rien modifier
590
- if (isBinary(input)) {
591
- return input;
592
- }
593
- // Si c'est un tableau, on traite chaque élément
594
- if (Array.isArray(input)) {
595
- return input.map(internal);
596
- }
597
- // Si c'est un plain object, on crée un nouvel objet avec remplacement dans les clés et valeurs
598
- if (isPlainObject(input)) {
599
- const result: any = {};
600
- for (const [key, value] of Object.entries(input)) {
601
- const resolvedKey = resolveString(key);
602
- result[resolvedKey] = internal(value);
603
- }
604
- return result;
605
- }
606
- // Si c'est une string, on applique le remplacement
607
- if (typeof input === "string") {
608
- return resolveString(input);
609
- }
610
- // Sinon (nombre, booléen, etc.), on retourne la valeur telle quelle
611
- return input;
612
- };
613
-
614
- return internal(obj);
615
- }
616
-
617
-
618
- private _cleanSchemaLeftoverAlias(obj: any): any {
619
- // Expression régulière qui détecte n'importe quel alias du type "@quelqueChose"
620
- const aliasRegex = /@\w+/;
621
-
622
- if (Array.isArray(obj)) {
623
- return obj.map((item: any) => this._cleanSchemaLeftoverAlias(item));
624
- } else if (obj && typeof obj === "object") {
625
- const newObj: any = {};
626
- for (const key of Object.keys(obj)) {
627
- const val = obj[key];
628
-
629
- // Si le nom de la clé contient un alias (exemple : "@userId", "@anything"), on ne la copie pas
630
- if (aliasRegex.test(key)) {
631
- continue;
632
- }
633
-
634
- // Si la propriété s'appelle "default"
635
- if (key === "default") {
636
- // Si la valeur est une chaîne et qu'elle contient un alias, on ne la copie pas
637
- if (typeof val === "string" && aliasRegex.test(val)) {
638
- continue;
639
- }
640
- // Si la valeur est un objet, on le nettoie récursivement
641
- if (val && typeof val === "object") {
642
- const cleanedDefault = this._cleanSchemaLeftoverAlias(val);
643
- // On ajoute seulement si l'objet nettoyé n'est pas vide
644
- if (Object.keys(cleanedDefault).length > 0) {
645
- newObj[key] = cleanedDefault;
646
- }
647
- continue;
648
- }
649
- }
650
-
651
- // Si la valeur est une chaîne contenant un alias, on ne la copie pas
652
- if (typeof val === "string" && aliasRegex.test(val)) {
653
- continue;
654
- }
655
-
656
- // Sinon, on nettoie récursivement la valeur
657
- newObj[key] = this._cleanSchemaLeftoverAlias(val);
658
- }
659
- return newObj;
660
- }
661
- return obj;
662
- }
663
-
664
- /**
665
- * Safely calls an asynchronous function and handles any errors that occur.
666
- * Logs the error message using the instance's logger before re-throwing the error.
667
- *
668
- * @param fn - The asynchronous function to be called.
669
- * @param args - The arguments to pass to the function.
670
- * @returns The result of the asynchronous function.
671
- * @throws {Error} Re-throws any error that occurs during the function execution.
672
- */
673
- async safeCall(fn: (...args: any[]) => Promise<any>, ...args: any[]) {
674
- try {
675
- return await fn(...args);
676
- } catch (error) {
677
- this._logger.error(`[ApiClient.safeCall] Erreur: ${getErrorMessage(error)}`);
678
- throw error;
679
- }
680
- }
681
-
682
- /**
683
- * Appelle un endpoint avec validations AJV, auth et circuit breaker.
684
- * En cas d’indisponibilité (offline ou breaker), **met en file** l’action via
685
- * `_offlineClientManager` si présent.
686
- *
687
- * @param constant - The constant representing the endpoint to call.
688
- * @param data - Données envoyées (incluant éventuellement `pathParams`).
689
- * @param transformResponseData - `true` (transforme via `_transformData`), `false`, ou une fonction `(data) => any`.
690
- * @param validateResponseSchema - Whether to validate the response schema.
691
- * @returns The response from the endpoint call, or an enqueue result if offline or breaker is active.
692
- * @throws {CircuitBreakerError} If the circuit breaker is activated.
693
- * @throws {ApiClientError} If the endpoint is not found, token is required but not provided, or validation fails.
694
- */
695
- async callEndpoint<T = any>(
696
- constant: string,
697
- data: CallEndpointData = {},
698
- transformResponseData: boolean | ((data: any) => any) = true,
699
- validateResponseSchema: boolean = true
700
- ): Promise<CallEndpointResponse<T>> {
701
-
702
- const endpoint = this._endpoints.find((ep) => ep.constant === constant);
703
- if (!endpoint) {
704
- throw new ApiClientError(`Endpoint introuvable : ${constant}`, 404);
705
- }
706
-
707
- const {
708
- path,
709
- method,
710
- contentType,
711
- auth,
712
- pathParams: pathSchema,
713
- request: requestSchema,
714
- responses
715
- } = endpoint;
716
-
717
- const lowerMethod = (method || "GET").toLowerCase();
718
- const realContentType = contentType || "application/json";
719
- const headers: Record<string, string> = { "Content-Type": realContentType };
720
- if (this._mode === "clean") {
721
- headers["X-Coco-Mode"] = "clean";
722
- }
723
-
724
- // Auth headers
725
- if (this._accessToken) {
726
- if (auth === "bearer") {
727
- headers["Authorization"] = `Bearer ${this._accessToken}`;
728
- } else if (!auth || auth === "none") {
729
- if (!headers["Authorization"]) {
730
- headers["Authorization"] = `Bearer ${this._accessToken}`;
731
- }
732
- }
733
- } else if (auth === "bearer") {
734
- throw new ApiClientError(`Token requis pour l'endpoint sécurisé : ${constant}`, 401);
735
- }
736
-
737
- // === 1. PathParams ===
738
- let resolvedPath = path;
739
- let resolvedParams = {};
740
- if (pathSchema) {
741
-
742
- // Si auth est "none" et que userId n'est pas défini, on nettoie le schéma
743
- let schemaToCompile = pathSchema;
744
- if (auth === "none" && this.userId === null) {
745
- schemaToCompile = this._cleanSchemaLeftoverAlias(pathSchema);
746
- }
747
-
748
- const pathParams = data.pathParams || {};
749
- const validatePathParams: ValidateFunction = this._ajv.compile(schemaToCompile);
750
- const valid = validatePathParams(pathParams);
751
-
752
- if (!valid) {
753
- const errorMessages = this._ajvErrorHuman(validatePathParams.errors ?? []);
754
- this.emit("validationError", { stage: "pathParams", errors: validatePathParams.errors });
755
- throw new ApiValidationError(`callEndpoint: ${constant} - Path parameter validation failed.`, 400, errorMessages, validatePathParams.errors);
756
- }
757
-
758
- resolvedParams = this._resolveSpecialValuesInPlace(pathParams);
759
-
760
- resolvedPath = resolvedPath.replace(/\{(\w+)\}/g, (_: any, key: string) => {
761
- const val = (resolvedParams as any)[key];
762
- if (val !== undefined) return encodeURIComponent(val);
763
- throw new ApiClientError(`Path param manquant ou non résolu : {${key}}`, 400);
764
- });
765
- }
766
-
767
- // === 2. Validation données (request schema) ===
768
- if (requestSchema) {
769
- // Si auth est "none" et que userId n'est pas défini, on nettoie le schéma
770
- let schemaToCompile = requestSchema;
771
- if (auth === "none" && this.userId === null) {
772
- schemaToCompile = this._cleanSchemaLeftoverAlias(requestSchema);
773
- }
774
-
775
- // Enveloppe costum (canal Symbol, cf. CostumScope) — injection UNIFIÉE données + contexte :
776
- // - `properties` : champs DATA costum (per-costum) fusionnés dans le schéma racine compilé
777
- // (`additionalProperties:false` ne voit que les properties du même objet schema, pas les allOf) ;
778
- // - `context` : costumSlug/costumId/costumType acceptés par l'AJV (props) PUIS injectés dans le payload
779
- // plus bas → le contrat n'a plus à déclarer le contexte costum.
780
- const costumEnv = (data as Record<symbol, unknown>)[COSTUM_REQUEST_ENVELOPE] as CostumRequestEnvelope | undefined;
781
- if (costumEnv) {
782
- const ctxProps = Object.fromEntries(Object.keys(costumEnv.context ?? {}).map((k) => [k, { type: "string" }]));
783
- if (costumEnv.isUpdate) {
784
- // UPDATE costum via `element/save` (pendant de l'ADD) : on n'envoie que les champs MODIFIÉS
785
- // (standard + costum, schémas portés dans `properties`). On compile un schéma PERMISSIF — pas
786
- // les `required`/`default` du schéma de création (sinon échec sur `name`/`type`/… non envoyés et
787
- // réinjection de `parent` par défaut). $defs conservés pour les éventuels $ref des properties.
788
- schemaToCompile = {
789
- type: "object",
790
- $defs: { ...((schemaToCompile as { $defs?: Record<string, unknown> }).$defs ?? {}), ...(costumEnv.defs ?? {}) },
791
- properties: { ...(costumEnv.properties ?? {}), ...ctxProps, id: { type: "string" }, collection: { type: "string" } },
792
- additionalProperties: false,
793
- };
794
- } else {
795
- schemaToCompile = {
796
- ...schemaToCompile,
797
- properties: { ...(schemaToCompile.properties ?? {}), ...(costumEnv.properties ?? {}), ...ctxProps },
798
- };
799
- }
800
- }
801
-
802
- let dataForValidation = { ...data };
803
- delete dataForValidation.pathParams;
804
- delete (dataForValidation as Record<symbol, unknown>)[COSTUM_REQUEST_ENVELOPE];
805
- // contexte costum injecté dans le payload (auto) → lu par resolveActiveCostum côté backend.
806
- if (costumEnv?.context) Object.assign(dataForValidation, costumEnv.context);
807
-
808
- // Normalize Dates to ISO strings for x-www-form-urlencoded BEFORE validation
809
- if (realContentType === "application/x-www-form-urlencoded") {
810
- dataForValidation = ApiClient.normalizeDatesForValidation(dataForValidation);
811
- // For fields with format:"date", extract only YYYY-MM-DD
812
- dataForValidation = ApiClient.normalizeSchemaDateFields(dataForValidation, schemaToCompile);
813
- }
814
-
815
- const cleanedData = ApiClient.stripNullsInPlace(dataForValidation);
816
-
817
- // UPDATE costum (enveloppe.isUpdate) : AJV SANS useDefaults — ne réinjecte aucun défaut de création.
818
- const requestAjv = costumEnv?.isUpdate ? this._ajvNoDefaults : this._ajv;
819
- const validateRequest: ValidateFunction = requestAjv.compile(schemaToCompile);
820
- const valid = validateRequest(cleanedData);
821
- if (!valid) {
822
- const errorMessages = validateRequest.errors ? this._ajvErrorHuman(validateRequest.errors) : [];
823
- this.emit("validationError", { stage: "request", errors: validateRequest.errors });
824
- throw new ApiValidationError(`callEndpoint: ${constant} - Request validation failed.`, 400, errorMessages , validateRequest.errors);
825
- }
826
-
827
- data = this._resolveSpecialValuesInPlace(cleanedData, resolvedParams);
828
- }
829
-
830
- // === 3. ENQUEUE SI OFFLINE (après validations, avant le breaker)
831
- if (this._offlineClientManager?.isOffline()) {
832
- if (typeof this._offlineClientManager._enqueueOfflineAction === "function") {
833
- this._logger.warn(`[ApiClient] Mode dégradé : mise en file de ${constant}`);
834
- await this._offlineClientManager._enqueueOfflineAction({
835
- constant,
836
- data,
837
- options: { transformResponseData, validateResponseSchema }
838
- });
839
-
840
- this._logger.info(`[ApiClient] Requête ${constant} mise en file (offline mode)`);
841
-
842
- this.emit("queuedOffline", {
843
- constant,
844
- reason: "offlineMode",
845
- data
846
- });
847
-
848
- const enqueued = { data: null, offline: true as const };
849
- return enqueued;
850
- } else {
851
- this._logger.warn("[ApiClient] Mode dégradé actif mais offlineManager non initialisé correctement");
852
- throw new ApiClientError("Mode hors-ligne actif, mais gestionnaire offline non disponible.", 503);
853
- }
854
- }
855
-
856
- // === 4. Circuit Breaker
857
- if (!this._checkCircuitBreaker()) {
858
- if (
859
- this._offlineClientManager?.isOffline?.() === false && // pas déjà en mode offline
860
- typeof this._offlineClientManager._enqueueOfflineAction === "function"
861
- ) {
862
- this._logger.warn(`[ApiClient] Circuit breaker actif → mise en file de ${constant}`);
863
- await this._offlineClientManager._enqueueOfflineAction({
864
- constant,
865
- data,
866
- options: { transformResponseData, validateResponseSchema }
867
- });
868
-
869
- this._logger.info(`[ApiClient] Requête ${constant} mise en file (circuit breaker)`);
870
-
871
- this.emit("queuedOffline", {
872
- constant,
873
- reason: "circuitBreaker",
874
- data
875
- });
876
-
877
- const enqueued = { data: null, breaker: true as const };
878
- return enqueued;
879
- }
880
-
881
- throw new CircuitBreakerError("Le circuit breaker est activé, impossible d'appeler l'API");
882
- }
883
-
884
- // === 5. Payload ===
885
- let payload;
886
- if (realContentType === "application/json" || realContentType === "multipart/form-data") {
887
- payload = data;
888
- } else if (realContentType === "application/x-www-form-urlencoded") {
889
- payload = ApiClient.toURLSearchParams(data);
890
- }
891
-
892
- this._logger.debug(`[ApiClient] callEndpoint: ${constant} -> ${resolvedPath}, method=${lowerMethod}`);
893
-
894
- try {
895
- const response = await this._client.request({
896
- url: resolvedPath,
897
- method: lowerMethod,
898
- headers,
899
- [lowerMethod === "get" ? "params" : "data"]: payload
900
- });
901
-
902
- if (this._mode === "clean") {
903
- // Enveloppe serveur {success, data|error} : unwrap + erreur typée. Les normalisations
904
- // sont faites PAR LE SERVEUR (serializeClean) — pas de _transformData ni d'AJV legacy ici.
905
- const body = response.data;
906
- if (body && typeof body === "object" && !Array.isArray(body) && typeof body.success === "boolean") {
907
- if (body.success === false) {
908
- throw new ApiResponseError(body?.error?.message ?? "Erreur inconnue", response.status, body);
909
- }
910
- response.data = body.data;
911
- }
912
- if (this._fromJSONValue) {
913
- response.data = this._reviveClean(response.data);
914
- }
915
- } else if (validateResponseSchema) {
916
- const statusStr = String(response.status);
917
- const schema = responses?.[statusStr];
918
-
919
- if (schema) {
920
- const validateResponse: ValidateFunction = this._ajv.compile(schema);
921
- const valid = validateResponse(response.data);
922
- if (!valid) {
923
- const errorMessages = this._ajvErrorHuman(validateResponse.errors ?? []);
924
- this.emit("validationError", { stage: "response", errors: validateResponse.errors });
925
- throw new ApiValidationError("Response validation failed.", response.status, errorMessages, validateResponse.errors);
926
- }
927
- }
928
- }
929
-
930
- this._resetCircuitBreakerSuccess();
931
-
932
- if (typeof transformResponseData === "function") {
933
- response.data = transformResponseData(response.data);
934
- } else if (transformResponseData === true && this._mode !== "clean") {
935
- response.data = this._transformData(response.data);
936
- }
937
-
938
- // postActions éventuelles
939
- if (Array.isArray(endpoint.postActions)) {
940
- endpoint.postActions.forEach((action: any) => {
941
- const value = action.path ? this._getValueByPath(response.data, action.path) : null;
942
-
943
- switch (action.type) {
944
- case "setToken":
945
- this.setToken(value);
946
- break;
947
-
948
- case "setRefreshToken":
949
- this.setRefreshToken(value);
950
- break;
951
-
952
- // case "setUserId":
953
- // this._setUserId(value);
954
- // break;
955
-
956
- case "resetSession":
957
- this.resetSession();
958
- break;
959
-
960
- case "emitEvent":
961
- if (action.event) {
962
- this.emit(action.event, value);
963
- this._logger.debug(`[ApiClient] Event émis : ${action.event}`, value);
964
- }
965
- break;
966
-
967
- case "callMethod":
968
- if (typeof (this as any)[action.method] === "function") {
969
- (this as any)[action.method]();
970
- this._logger.debug(`[ApiClient] Méthode appelée : ${action.method}`);
971
- } else {
972
- this._logger.warn(`[ApiClient] Méthode inconnue : ${action.method}`);
973
- }
974
- break;
975
-
976
- default:
977
- this._logger.warn(`[ApiClient] Action inconnue : ${action.type}`);
978
- }
979
- });
980
- }
981
-
982
-
983
- return response;
984
- } catch (error) {
985
- if (error instanceof ApiResponseError) {
986
- // Erreur MÉTIER (enveloppe du mode clean success:false) : même sémantique que le
987
- // checkAndThrowApiResponseError legacy -> re-throw tel quel, et SANS compter dans le
988
- // circuit breaker (en legacy ces erreurs arrivent en HTTP 200 et ne l'incrémentent pas).
989
- throw error;
990
- }
991
- this._updateCircuitBreakerError();
992
- this._logger.error(`[ApiClient] Erreur lors de l'appel de ${constant}: ${getErrorMessage(error)}`);
993
- if(error instanceof ApiValidationError) {
994
- throw error;
995
- } else {
996
- const statusCode = hasResponse(error) ? error.response.status : 500;
997
- const responseData = hasResponse(error) ? error.response.data : null;
998
- throw new ApiClientError(
999
- `Erreur lors de l'appel de l'API : ${getErrorMessage(error)}`,
1000
- statusCode,
1001
- responseData
1002
- );
1003
- }
1004
-
1005
- }
1006
- }
1007
-
1008
- /**
1009
- * Converts AJV (Another JSON Schema Validator) errors into human-readable messages.
1010
- *
1011
- * @param errors - An array of AJV validation error objects.
1012
- * @returns An array of human-readable error messages extracted from the AJV errors.
1013
- */
1014
- private _ajvErrorHuman(errors: any[]){
1015
- try {
1016
- const errorsMessages = new AggregateAjvError(errors);
1017
- // @ts-expect-error - champ non typé public dans les defs
1018
- const messages = errorsMessages.errors.map(({ message }: any) => message);
1019
- return messages;
1020
- } catch (e) {
1021
- this._logger.error("[ApiClient] _ajvErrorHuman", e);
1022
- return errors.map(({ message }: any) => message);
1023
- }
1024
- }
1025
-
1026
- /**
1027
- * Checks the API response for errors and throws an `ApiResponseError` if any are found.
1028
- *
1029
- * This method examines the `response.data` object to determine if it contains
1030
- * error information. It handles both standard and nested error cases:
1031
- *
1032
- * - Standard case: If `data.result` is a boolean and is `false`, an error is thrown.
1033
- * - Nested case: If `data.resultErrors` exists and contains a `result` property
1034
- * that is a boolean and is `false`, an error is thrown.
1035
- *
1036
- * If no errors are found, the original `response` is returned.
1037
- *
1038
- * @param {import("axios").AxiosResponse | { data: any } } response
1039
- * @returns {import("axios").AxiosResponse | { data: any }}
1040
- * @throws {ApiResponseError} If an error is detected in the response data.
1041
- */
1042
- checkAndThrowApiResponseError(response: any) {
1043
- if (!("status" in response)) {
1044
- return response;
1045
- }
1046
-
1047
- const data = response.data;
1048
- if (!data || typeof data !== "object") {
1049
- return response;
1050
- }
1051
-
1052
- // Cas standard : vérifie si data.result vaut false
1053
- if (typeof data.result === "boolean" && data.result === false) {
1054
- throw new ApiResponseError(
1055
- data.msg || "Erreur inconnue",
1056
- response.status,
1057
- data
1058
- );
1059
- }
1060
-
1061
- // Cas imbriqué : vérifie si data.resultErrors existe et contient une erreur
1062
- if (data.resultErrors && typeof data.resultErrors === "object") {
1063
- if (typeof data.resultErrors.result === "boolean" && data.resultErrors.result === false) {
1064
- throw new ApiResponseError(
1065
- data.resultErrors.msg || "Erreur inconnue",
1066
- response.status,
1067
- data
1068
- );
1069
- }
1070
- }
1071
-
1072
- return response;
1073
- }
1074
-
1075
- /**
1076
- * Réinitialise complètement la session de l'utilisateur :
1077
- * - Token d'accès
1078
- * - Token de rafraîchissement
1079
- * - userId
1080
- * - En-têtes Axios
1081
- */
1082
- resetSession(): void {
1083
- this.setToken(null);
1084
- this.setRefreshToken(null);
1085
- this._setUserId(null);
1086
- this._tokenStorage.clear();
1087
-
1088
- // Suppression des en-têtes
1089
- delete this._client.defaults.headers.common["Authorization"];
1090
-
1091
- this._logger.info("[ApiClient] Session utilisateur réinitialisée.");
1092
- this.emit("sessionReset");
1093
- }
1094
-
1095
- /**
1096
- * Retrieves the value from an object based on a dot-separated path.
1097
- */
1098
- private _getValueByPath(obj: any, path: string): any {
1099
- if (!path) return undefined;
1100
- const keys = path.split(".");
1101
- return keys.reduce((acc, key) => acc && acc[key], obj);
1102
- }
1103
-
1104
- // Conversions
1105
-
1106
- /**
1107
- * Normalizes Date objects to ISO strings for AJV validation.
1108
- * This preserves the object structure while converting only Dates.
1109
- * Handles reactive Proxies by unwrapping them first.
1110
- *
1111
- * @param obj - The object to normalize
1112
- * @returns The normalized object with Dates as ISO strings
1113
- */
1114
- static normalizeDatesForValidation(obj: any): any {
1115
- if (obj === null || obj === undefined) return obj;
1116
-
1117
- // Unwrap reactive Proxies BEFORE checking for Date
1118
- if (obj && typeof obj === "object" && obj.__isReactive && obj.__raw) {
1119
- return ApiClient.normalizeDatesForValidation(obj.__raw);
1120
- }
1121
-
1122
- if (obj instanceof Date) {
1123
- return obj.toISOString().replace(/\.\d{3}Z$/, "+0000");
1124
- }
1125
-
1126
- if (Array.isArray(obj)) {
1127
- return obj.map((item: any) => ApiClient.normalizeDatesForValidation(item));
1128
- }
1129
-
1130
- if (typeof obj === "object") {
1131
- const normalized: any = {};
1132
- for (const key of Object.keys(obj)) {
1133
- normalized[key] = ApiClient.normalizeDatesForValidation(obj[key]);
1134
- }
1135
- return normalized;
1136
- }
1137
-
1138
- return obj;
1139
- }
1140
-
1141
- /**
1142
- * Normalizes fields with format:"date" in schema to YYYY-MM-DD format.
1143
- * Extracts only the date part from ISO datetime strings.
1144
- *
1145
- * @param data - The data object to normalize
1146
- * @param schema - The JSON schema containing field definitions
1147
- * @returns The normalized data with date-only strings for format:"date" fields
1148
- */
1149
- static normalizeSchemaDateFields(data: any, schema: any) {
1150
- if (!data || !schema || typeof data !== "object") return data;
1151
-
1152
- const normalized = { ...data };
1153
-
1154
- // Récupérer les propriétés du schéma (gérer allOf, then, properties, etc.)
1155
- let properties = schema.properties || {};
1156
-
1157
- // Si le schéma utilise allOf, parcourir tous les éléments
1158
- if (schema.allOf && Array.isArray(schema.allOf)) {
1159
- for (const allOfItem of schema.allOf) {
1160
- // Chercher dans properties directes
1161
- if (allOfItem.properties) {
1162
- properties = { ...properties, ...allOfItem.properties };
1163
- }
1164
- // Chercher dans if/then/properties
1165
- if (allOfItem.then && allOfItem.then.properties) {
1166
- properties = { ...properties, ...allOfItem.then.properties };
1167
- }
1168
- }
1169
- }
1170
-
1171
- // Aussi vérifier schema.then.properties si présent
1172
- if (schema.then && schema.then.properties) {
1173
- properties = { ...properties, ...schema.then.properties };
1174
- }
1175
-
1176
- // Pour chaque propriété ayant format:"date"
1177
- for (const [key, propDef] of Object.entries(properties)) {
1178
- const typedPropDef = propDef as any;
1179
- if (typedPropDef?.format === "date" && normalized[key]) {
1180
- const value = normalized[key];
1181
-
1182
- // Si c'est une string ISO datetime, extraire YYYY-MM-DD
1183
- if (typeof value === "string" && value.includes("T")) {
1184
- normalized[key] = value.split("T")[0];
1185
- }
1186
- }
1187
- }
1188
-
1189
- return normalized;
1190
- }
1191
-
1192
- /**
1193
- * Converts an object to URL search parameters.
1194
- *
1195
- * @param obj - The object to be converted to URL search parameters.
1196
- * @param [options={}] - Optional settings for the conversion.
1197
- * @returns The URL search parameters generated from the object.
1198
- */
1199
- static toURLSearchParams(obj: any, options: any = {}) {
1200
- return this._buildParams(obj, new URLSearchParams(), options) as URLSearchParams;
1201
- }
1202
-
1203
- /**
1204
- * Builds parameters for an API request from a given object.
1205
- */
1206
- static _buildParams(
1207
- obj: any,
1208
- paramsInstance: FormData | URLSearchParams,
1209
- options: {
1210
- dots?: boolean;
1211
- indexes?: boolean;
1212
- metaTokens?: boolean;
1213
- } = {}
1214
- ): FormData | URLSearchParams {
1215
- if (typeof obj !== "object" || obj === null) {
1216
- throw new TypeError("La donnée doit être un objet non nul.");
1217
- }
1218
-
1219
- const { dots = false, indexes = false, metaTokens = true } = options;
1220
- const stack: any[] = [];
1221
-
1222
- function isVisitable(thing: any) {
1223
- return Object.prototype.toString.call(thing) === "[object Object]" || Array.isArray(thing);
1224
- }
1225
-
1226
- function removeBrackets(key: any) {
1227
- return key.endsWith("[]") ? key.slice(0, -2) : key;
1228
- }
1229
-
1230
- function renderKey(path: any, key: any, useDots = false) {
1231
- if (!path) return key;
1232
- return path.concat(key).map((token: any, i: any) => {
1233
- token = removeBrackets(token);
1234
- return !useDots && i ? `[${token}]` : token;
1235
- }).join(useDots ? "." : "");
1236
- }
1237
-
1238
- function convertValue(value: any) {
1239
- if (value === null || value === undefined) return "";
1240
- if (value instanceof Date) return value.toISOString();
1241
- if (
1242
- value instanceof Blob ||
1243
- value instanceof File ||
1244
- value instanceof ArrayBuffer ||
1245
- ArrayBuffer.isView(value)
1246
- ) {
1247
- return value;
1248
- }
1249
- return value;
1250
- }
1251
-
1252
- function defaultVisitor(value: any, key: any, path: any) {
1253
- if (value && typeof value === "object") {
1254
- if (key.endsWith("{}")) {
1255
- key = metaTokens ? key : key.slice(0, -2);
1256
- paramsInstance.append(renderKey(path, key, dots), JSON.stringify(value));
1257
- return false;
1258
- }
1259
-
1260
- if ((Array.isArray(value) && !value.some(isVisitable)) || key.endsWith("[]")) {
1261
- key = removeBrackets(key);
1262
- value.forEach((el: any, index: any) => {
1263
- if (el !== undefined && el !== null) {
1264
- // const fieldKey = indexes ? renderKey([key], index, dots) : `${key}[]`;
1265
- const fieldKey = indexes
1266
- ? renderKey(path.concat(key), index, dots)
1267
- : `${renderKey(path, key, dots)}[]`;
1268
- paramsInstance.append(fieldKey, convertValue(el));
1269
- }
1270
- });
1271
- return false;
1272
- }
1273
- }
1274
-
1275
- if (isVisitable(value)) return true;
1276
- paramsInstance.append(renderKey(path, key, dots), convertValue(value));
1277
- return false;
1278
- }
1279
-
1280
- function build(value: any, path: any = []) {
1281
- if (value === undefined || value === null || (stack as any).includes(value)) return;
1282
- (stack as any).push(value);
1283
-
1284
- Object.entries(value).forEach(([k, el]) => {
1285
- if (el !== undefined && defaultVisitor(el, k.trim(), path)) {
1286
- build(el, path.concat(removeBrackets(k)));
1287
- }
1288
- });
1289
-
1290
- stack.pop();
1291
- }
1292
-
1293
- build(obj);
1294
- return paramsInstance;
1295
- }
1296
-
1297
-
1298
- /**
1299
- * Transforms the given data based on its structure.
1300
- *
1301
- * This method normalizes various structures of data into a consistent format.
1302
- * It handles different types of data including objects, arrays, and specific
1303
- * nested structures like `resultGoods`, `resultErrors`, `results`, `news`,
1304
- * `notif`, `citoyens`, `organizations`, `cities`, `newComment`, `map`, and `object`.
1305
- *
1306
- * @param data - The data to be transformed.
1307
- * @returns - The transformed data.
1308
- *
1309
- * @private
1310
- */
1311
- /**
1312
- * Reviver du mode clean : redonne à l'app le shape exact qu'elle recevait après les
1313
- * normalisations legacy de la lib — dates ISO (champs _dateFields) -> instances Date,
1314
- * `id` oid-hex -> `_id` instance ObjectID (créée via EJSON.fromJSONValue, MÊME chemin
1315
- * que le mode legacy). Le serveur clean a déjà fait tout le reste.
1316
- */
1317
- private _reviveClean(data: any): any {
1318
- if (Array.isArray(data)) {
1319
- return data.map((item) => this._reviveClean(item));
1320
- }
1321
- if (data !== null && typeof data === "object") {
1322
- const out: any = {};
1323
- Object.keys(data).forEach((key) => {
1324
- let value = this._reviveClean(data[key]);
1325
- if (this._dateFields.includes(key) && typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
1326
- const d = new Date(value);
1327
- if (!Number.isNaN(d.getTime())) value = d;
1328
- }
1329
- out[key] = value;
1330
- });
1331
- if (typeof out.id === "string" && /^[0-9a-fA-F]{24}$/.test(out.id) && out._id === undefined) {
1332
- out._id = EJSON.fromJSONValue({ $type: "oid", $value: out.id });
1333
- }
1334
- return out;
1335
- }
1336
- return data;
1337
- }
1338
-
1339
- private _transformData(data: any): any {
1340
- if (data && typeof data === "object") {
1341
- if (data.resultGoods?.msg) {
1342
- data = this._normalizeJsonData({ msg: data.resultGoods.msg, ...data });
1343
- } else if (data.resultErrors?.msg) {
1344
- data = this._normalizeJsonData({ msg: data.resultErrors.msg, ...data });
1345
- } else if (data.results && typeof data.results === "object" && !Array.isArray(data.results)) {
1346
- data.results = Object.keys(data.results).map((key) => {
1347
- return this._normalizeJsonData({ id: key, ...data.results[key] });
1348
- });
1349
- } else if (Array.isArray(data.results) && data.results.length > 0) {
1350
- data.results = data.results.map((item: any) => this._normalizeJsonData(item));
1351
- } else if (data.news && Array.isArray(data.news) && data.news.length === 0) {
1352
- data = data.news;
1353
- } else if (data.news && typeof data.news === "object" && !Array.isArray(data.news)) {
1354
- data = Object.keys(data.news).map((key) => {
1355
- return this._normalizeJsonData({ id: key, ...data.news[key] });
1356
- });
1357
- } else if (data.notif && typeof data.notif === "object" && !Array.isArray(data.notif)) {
1358
- data.notif = Object.keys(data.notif).map((key) => {
1359
- return this._normalizeJsonData({ id: key, ...data.notif[key] });
1360
- });
1361
- } else if (
1362
- data.citoyens && typeof data.citoyens === "object" && !Array.isArray(data.citoyens) &&
1363
- data.organizations && typeof data.organizations === "object" && !Array.isArray(data.organizations)
1364
- ) {
1365
- data = [
1366
- ...Object.keys(data.citoyens).map(key => this._normalizeJsonData({ id: key, ...data.citoyens[key] })),
1367
- ...Object.keys(data.organizations).map(key => this._normalizeJsonData({ id: key, ...data.organizations[key] }))
1368
- ];
1369
- } else if (
1370
- data.citoyens && typeof data.citoyens === "object" && !Array.isArray(data.citoyens)
1371
- ) {
1372
- data = Object.keys(data.citoyens).map(key => this._normalizeJsonData({ id: key, ...data.citoyens[key] }));
1373
- } else if (data.cities && typeof data.cities === "object" && !Array.isArray(data.cities)) {
1374
- data = Object.keys(data.cities).map(key => this._normalizeJsonData({ id: key, ...data.cities[key] }));
1375
- } else if (data.cities && Array.isArray(data.cities) && data.cities.length === 0) {
1376
- data = data.cities;
1377
- } else if (data.newComment && typeof data.newComment === "object") {
1378
- data.newComment = this._normalizeJsonData({ ...data.newComment });
1379
- } else if (Array.isArray(data)) {
1380
- data = data.map((item) => this._normalizeJsonData(item));
1381
- } else if (data.map && typeof data.map === "object") {
1382
- data.map = this._normalizeJsonData(data.map);
1383
- } else if (data.object && typeof data.object === "object") {
1384
- data.object = this._normalizeJsonData(data.object);
1385
- } else if (typeof data === "object" && Object.keys(data).length > 0) {
1386
- // Vérifie si toutes les clés sont des IDs mongo
1387
- const allMongoIds = Object.keys(data).every((key) => /^[0-9a-fA-F]{24}$/.test(key));
1388
- if (allMongoIds) {
1389
- data = Object.keys(data).map((key) => ({
1390
- id: key,
1391
- ...this._normalizeJsonData(data[key])
1392
- }));
1393
- } else {
1394
- data = this._normalizeJsonData(data);
1395
- }
1396
- }
1397
- }
1398
- return data;
1399
- }
1400
-
1401
-
1402
- /**
1403
- * Normalizes JSON data by transforming specific fields and ensuring URLs are complete.
1404
- *
1405
- * @param item - The JSON object to be normalized.
1406
- * @returns - The normalized JSON object.
1407
- *
1408
- * The function performs the following transformations:
1409
- * - Normalizes the ID field if it matches a specific pattern.
1410
- * - Converts date fields from seconds to milliseconds.
1411
- * - Ensures URLs in specific fields are complete.
1412
- * - Filters and normalizes nested objects and arrays.
1413
- * - Removes the `timeAgo` field if it exists.
1414
- * - Converts the object to EJSON format if necessary.
1415
- */
1416
- private _normalizeJsonData(item: any) {
1417
- if (!item || typeof item !== "object") {
1418
- return item;
1419
- }
1420
-
1421
- // Application des normalisations récursives
1422
- item = this._normalizeRecursively(item);
1423
-
1424
- // item = this._normalizeIdRecursively(item);
1425
- // item = this._normalizeDatesRecursively(item);
1426
- // item = this._normalizeImagesRecursively(item);
1427
-
1428
- // if(item?.preferences){
1429
- // item.preferences = this._normalizeBooleansRecursively(item.preferences);
1430
- // }
1431
-
1432
- if (item?.mediaImg?.images && Array.isArray(item.mediaImg.images) && item.mediaImg.images.length > 0) {
1433
- item.mediaImg.countImages = item.mediaImg.images.length;
1434
- }
1435
-
1436
- if (item?.mediaFile?.files && Array.isArray(item.mediaFile.files) && item.mediaFile.files.length > 0) {
1437
- item.mediaFile.countFiles = item.mediaFile.files.length;
1438
- }
1439
-
1440
- if (
1441
- item?.src?.changes &&
1442
- typeof item.src.changes === "object" &&
1443
- Object.keys(item.src.changes).length > 0
1444
- ) {
1445
- Object.keys(item.src.changes).forEach((key) => {
1446
- if (item.src.changes[key]) {
1447
- item.src.changes[key] = this._ensureFullURL(item.src.changes[key]);
1448
- }
1449
- });
1450
- }
1451
-
1452
- if (item?.openingHours && Array.isArray(item.openingHours) && item.openingHours.length > 0) {
1453
- item.openingHours = item.openingHours.filter(
1454
- (day: any) => day.dayOfWeek && day.hours && day.hours[0] && day.hours[0].opens
1455
- );
1456
- }
1457
-
1458
- if (item.replies && typeof item.replies === "object" && Object.keys(item.replies).length > 0) {
1459
- item.replies = Object.keys(item.replies).map((key) => {
1460
- return this._normalizeJsonData({ id: key, ...item.replies[key] });
1461
- });
1462
- }
1463
-
1464
- if (
1465
- item?.author &&
1466
- typeof item.author === "object" &&
1467
- Object.keys(item.author).length > 0
1468
- ) {
1469
- // Si author contient déjà une propriété attendue (ici "profilThumbImageUrl"),
1470
- // on considère qu'il est déjà aplatit.
1471
- if (!("profilThumbImageUrl" in item.author)) {
1472
- // Sinon, on considère que author est un mapping et on prend le premier sous-objet.
1473
- const firstKey = Object.keys(item.author)[0];
1474
- // On remplace author par le contenu du sous-objet
1475
- item.author = { ...item.author[firstKey] };
1476
- }
1477
- }
1478
-
1479
- // Supprime timeAgo
1480
- if (item?.timeAgo) {
1481
- delete item.timeAgo;
1482
- }
1483
-
1484
- if(item?.author?.typeSig === "people"){
1485
- item.author.type = "citoyens";
1486
- }
1487
-
1488
- if(item?.typeSig === "people"){
1489
- item.collection = "citoyens";
1490
- }
1491
- if(item?.typeSig === "organizations"){
1492
- item.collection = "organizations";
1493
- }
1494
-
1495
- // Convertit en EJSON si besoin
1496
- return this._fromJSONValue ? EJSON.fromJSONValue(item) : item;
1497
- }
1498
-
1499
-
1500
- /**
1501
- * Ensures that the given image path is a full URL. If the image path is already a full URL
1502
- * (i.e., it starts with "http://" or "https://"), it is returned as is. Otherwise, the image path
1503
- * is concatenated with the base URL of the ApiClient instance.
1504
- *
1505
- * @param imagePath - The image path to ensure as a full URL.
1506
- * @returns - The full URL of the image path.
1507
- */
1508
- private _ensureFullURL(imagePath: any) {
1509
- if (!imagePath) return imagePath;
1510
- imagePath = imagePath.trim();
1511
- if (!imagePath || /^https?:\/\//i.test(imagePath)) {
1512
- return imagePath;
1513
- }
1514
-
1515
- let separator = "/";
1516
- if (this._baseURL.endsWith("/") && imagePath.startsWith("/")) {
1517
- separator = "";
1518
- } else if (!this._baseURL.endsWith("/") && !imagePath.startsWith("/")) {
1519
- separator = "/";
1520
- } else {
1521
- separator = "";
1522
- }
1523
-
1524
- return `${this._baseURL}${separator}${imagePath}`;
1525
- }
1526
-
1527
- /**
1528
- * Parcourt récursivement un objet pour normaliser les champs de date.
1529
- * Pour chaque clé présente dans dateFields, si la valeur est un nombre ou un objet
1530
- * contenant une propriété sec (nombre), la transforme en { $date: valeurEnMs }.
1531
- *
1532
- * @param obj - L’objet à normaliser.
1533
- * @returns L’objet normalisé.
1534
- */
1535
- // _normalizeDatesRecursively(obj) {
1536
- // if (obj === null || typeof obj !== "object") return obj;
1537
-
1538
- // // Si c'est un tableau, on traite chaque élément récursivement.
1539
- // if (Array.isArray(obj)) {
1540
- // return obj.map(item => this._normalizeDatesRecursively(item));
1541
- // }
1542
-
1543
- // Object.keys(obj).forEach((key) => {
1544
- // if (this._dateFields.includes(key)) {
1545
- // if (obj[key] && typeof obj[key] === "object" && obj[key].sec && typeof obj[key].sec === "number") {
1546
- // obj[key] = { $date: obj[key].sec * 1000 };
1547
- // } else if (typeof obj[key] === "number") {
1548
- // obj[key] = { $date: obj[key] * 1000 };
1549
- // }
1550
- // }
1551
- // if (obj[key] && typeof obj[key] === "object") {
1552
- // this._normalizeDatesRecursively(obj[key]);
1553
- // }
1554
- // });
1555
- // return obj;
1556
- // }
1557
-
1558
- /**
1559
- * Parcourt récursivement un objet pour normaliser les chemins d'images.
1560
- * Pour chaque propriété correspondant à un champ d'image (dans imageFields),
1561
- * vérifie et convertit le chemin en URL complète à l’aide de _ensureFullURL.
1562
- * Les cas particuliers (comme mediaImg.images ou mediaFile.files) sont également gérés.
1563
- *
1564
- * @param obj - L’objet à normaliser.
1565
- * @returns L’objet normalisé.
1566
- */
1567
- // _normalizeImagesRecursively(obj) {
1568
- // if (obj === null || typeof obj !== "object") return obj;
1569
-
1570
- // // Si c'est un tableau, on traite chaque élément récursivement.
1571
- // if (Array.isArray(obj)) {
1572
- // return obj.map(item => this._normalizeImagesRecursively(item));
1573
- // }
1574
-
1575
- // Object.keys(obj).forEach((key) => {
1576
- // if (this._imageFields.includes(key) && typeof obj[key] === "string" && obj[key].trim() !== "") {
1577
- // obj[key] = this._ensureFullURL(obj[key]);
1578
- // }
1579
-
1580
- // // Cas particulier pour content.image
1581
- // if (key === "content" && obj[key]?.image) {
1582
- // obj[key].image = this._ensureFullURL(obj[key].image);
1583
- // }
1584
-
1585
- // if (obj[key] && typeof obj[key] === "object") {
1586
- // this._normalizeImagesRecursively(obj[key]);
1587
- // }
1588
- // });
1589
- // return obj;
1590
- // }
1591
-
1592
- /**
1593
- * Parcourt récursivement un objet ou un tableau pour normaliser les identifiants (ID).
1594
- * Si un objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
1595
- * l'ID est normalisé et la propriété _id est convertie au format EJSON attendu.
1596
- *
1597
- * @param obj - L'objet ou le tableau à normaliser.
1598
- * @returns L'objet ou le tableau normalisé.
1599
- */
1600
- // _normalizeIdRecursively(obj) {
1601
- // if (obj === null || typeof obj !== "object") return obj;
1602
-
1603
- // // Si c'est un tableau, on traite chaque élément récursivement.
1604
- // if (Array.isArray(obj)) {
1605
- // return obj.map(item => this._normalizeIdRecursively(item));
1606
- // }
1607
-
1608
- // // Vérifier et normaliser l'ID dans l'objet
1609
- // if (obj._id && obj._id.$id && /^[0-9a-fA-F]{24}$/.test(obj._id.$id)) {
1610
- // obj.id = obj._id.$id;
1611
- // obj._id = { $type: "oid", $value: obj._id.$id };
1612
- // }
1613
- // if (obj.id && obj.id.$id && /^[0-9a-fA-F]{24}$/.test(obj.id.$id)) {
1614
- // obj._id = { $type: "oid", $value: obj.id.$id };
1615
- // obj.id = obj.id.$id;
1616
- // }
1617
-
1618
- // // Appel récursif pour chaque propriété de l'objet
1619
- // Object.keys(obj).forEach(key => {
1620
- // if (obj[key] && typeof obj[key] === "object") {
1621
- // obj[key] = this._normalizeIdRecursively(obj[key]);
1622
- // }
1623
- // });
1624
-
1625
- // return obj;
1626
- // }
1627
-
1628
- /**
1629
- * Normalise récursivement les valeurs booléennes.
1630
- * Si une valeur est une chaîne "true" ou "false", elle est convertie en booléen.
1631
- *
1632
- * @param data - L'objet, le tableau ou la valeur à normaliser.
1633
- * @returns La donnée normalisée.
1634
- */
1635
- // _normalizeBooleansRecursively(data) {
1636
- // if (typeof data === "string") {
1637
- // if (data === "true") return true;
1638
- // if (data === "false") return false;
1639
- // return data;
1640
- // }
1641
- // if (Array.isArray(data)) {
1642
- // return data.map(item => this._normalizeBooleansRecursively(item));
1643
- // }
1644
- // if (data !== null && typeof data === "object") {
1645
- // Object.keys(data).forEach(key => {
1646
- // data[key] = this._normalizeBooleansRecursively(data[key]);
1647
- // });
1648
- // }
1649
- // return data;
1650
- // }
1651
-
1652
- /**
1653
- * Transforme une chaîne "true" ou "false" en booléen.
1654
- *
1655
- * @param str - La chaîne à normaliser.
1656
- * @returns La valeur booléenne ou la chaîne originale.
1657
- */
1658
- private _normalizeString(str: any) {
1659
- if (str === "true") return true;
1660
- if (str === "false") return false;
1661
- return str;
1662
- }
1663
-
1664
- /**
1665
- * Normalise les identifiants dans un objet.
1666
- * Si l'objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
1667
- * il met à jour l'objet en assignant la valeur de l'ID et en formattant _id en EJSON.
1668
- *
1669
- * @param obj - L'objet à normaliser.
1670
- * @returns L'objet avec l'ID normalisé.
1671
- */
1672
- private _normalizeId(obj: any) {
1673
- if (obj._id && obj._id.$id && /^[0-9a-fA-F]{24}$/.test(obj._id.$id)) {
1674
- obj.id = obj._id.$id;
1675
- obj._id = { $type: "oid", $value: obj._id.$id };
1676
- }
1677
- if (obj.id && obj.id.$id && /^[0-9a-fA-F]{24}$/.test(obj.id.$id)) {
1678
- obj._id = { $type: "oid", $value: obj.id.$id };
1679
- obj.id = obj.id.$id;
1680
- }
1681
- return obj;
1682
- }
1683
-
1684
- /**
1685
- * Normalise une valeur de date.
1686
- * Si la valeur est un objet contenant "sec" (nombre) ou un nombre,
1687
- * elle est convertie en { $date: valeurEnMs }.
1688
- *
1689
- * @param value - La valeur à normaliser.
1690
- * @returns La valeur normalisée.
1691
- */
1692
- private _normalizeDate(value: any) {
1693
- if (value && typeof value === "object" && typeof value.sec === "number") {
1694
- return { $date: value.sec * 1000 };
1695
- } else if (typeof value === "number") {
1696
- return { $date: value * 1000 };
1697
- } else if (typeof value === "string" && value.trim() !== "") {
1698
- const date = new Date(value);
1699
- // Vérifier que la date est valide (pas NaN)
1700
- if (!isNaN(date.getTime())) {
1701
- return EJSON.toJSONValue(date);
1702
- }
1703
- }
1704
- return value;
1705
- }
1706
-
1707
- /**
1708
- * Normalise une URL d'image.
1709
- * Si la valeur est une chaîne non vide, retourne l'URL complète via _ensureFullURL.
1710
- *
1711
- * @param value - La valeur à normaliser.
1712
- * @returns La valeur normalisée.
1713
- */
1714
- private _normalizeImage(value: any) {
1715
- if (typeof value === "string" && value.trim() !== "") {
1716
- return this._ensureFullURL(value);
1717
- }
1718
- return value;
1719
- }
1720
-
1721
- /**
1722
- * Normalise les valeurs numériques renvoyées en string par le backend
1723
- * (cas typique : content-type application/x-www-form-urlencoded qui
1724
- * sérialise tout en string, et le backend stocke tel quel).
1725
- *
1726
- * @param value - La valeur à normaliser.
1727
- * @returns Number si conversion possible, sinon la valeur d'origine.
1728
- */
1729
- private _normalizeNumber(value: any) {
1730
- if (typeof value !== "string") return value;
1731
- const trimmed = value.trim();
1732
- if (trimmed === "") return value;
1733
- const n = Number(trimmed);
1734
- return Number.isFinite(n) ? n : value;
1735
- }
1736
-
1737
- /**
1738
- * Liste des champs d'image à normaliser.
1739
- */
1740
- private _imageFields = [
1741
- "profilImageUrl",
1742
- "profilThumbImageUrl",
1743
- "profilMediumImageUrl",
1744
- "profilMarkerImageUrl",
1745
- "profilBannerUrl",
1746
- "profilRealBannerUrl",
1747
- "imagePath",
1748
- "imageThumbPath",
1749
- "docPath",
1750
- "imageThumb",
1751
- "imageMediumPath",
1752
- "logo",
1753
- "logoMin",
1754
- // `image` : chemin relatif renvoyé par Document::getDocumentPath (ex: valeurs
1755
- // thématiques de COSTUM_FILTER_COFORM_BY_PATH). _normalizeImage ne touche que les
1756
- // strings non vides → "" et non-strings restent inchangés (pas d'effet de bord).
1757
- "image"
1758
- ];
1759
-
1760
- /**
1761
- * Liste des champs de date à normaliser.
1762
- */
1763
- // champs d'ENTITÉ génériques + champs costum (auto-générés depuis l'artefact via normalize.ts, coercés par NOM
1764
- // sans dépendre de source.key ; les champs au type divergent entre costums — phone/rate — en sont exclus).
1765
- private _dateFields = ["modified", "created", "updated", "birthDate", "lastLoginDate", "startDate", "endDate", "date", "issuedOn", ...COSTUM_DATE_FIELDS];
1766
-
1767
- /**
1768
- * Liste des champs numériques à normaliser (le backend les renvoie en string
1769
- * via x-www-form-urlencoded ; sans cette coercion, JSON.stringify diff
1770
- * produit des faux positifs dans `hasChanges()`).
1771
- */
1772
- private _numberFields = ["credits", "min", "max", "timeSpent", ...COSTUM_NUMBER_FIELDS];
1773
-
1774
- /**
1775
- * Normalise récursivement un objet, un tableau ou une valeur simple.
1776
- * Cette fonction réalise en une seule passe les opérations suivantes :
1777
- * - Conversion des chaînes "true"/"false" en booléens.
1778
- * - Normalisation des dates (pour les champs spécifiés).
1779
- * - Normalisation des images (pour les champs spécifiés).
1780
- * - Transformation récursive des objets et tableaux.
1781
- * - Normalisation des identifiants.
1782
- *
1783
- * @param data - La donnée à normaliser.
1784
- * @returns La donnée normalisée.
1785
- */
1786
- private _normalizeRecursively(data: any): any {
1787
- // Cas de base pour les chaînes
1788
- if (typeof data === "string") {
1789
- return this._normalizeString(data);
1790
- }
1791
-
1792
- // Traitement des tableaux
1793
- if (Array.isArray(data)) {
1794
- return data.map((item: any) => this._normalizeRecursively(item));
1795
- }
1796
-
1797
- // Traitement des objets non nuls
1798
- if (data !== null && typeof data === "object") {
1799
- // On crée une copie de l'objet pour éviter les effets de bord.
1800
- const normalizedData: any = {};
1801
-
1802
- Object.keys(data).forEach(key => {
1803
- // Appliquer récursivement la normalisation sur la valeur.
1804
- let value = this._normalizeRecursively(data[key]);
1805
-
1806
- // Si la clé correspond à un champ de date, on applique la normalisation de date.
1807
- if (this._dateFields.includes(key)) {
1808
- value = this._normalizeDate(value);
1809
- }
1810
-
1811
- // Si la clé correspond à un champ d'image, on applique la normalisation d'image.
1812
- if (this._imageFields.includes(key)) {
1813
- value = this._normalizeImage(value);
1814
- }
1815
-
1816
- // Si la clé correspond à un champ numérique, on coerce la string en number.
1817
- if (this._numberFields.includes(key)) {
1818
- value = this._normalizeNumber(value);
1819
- }
1820
-
1821
- // Cas particulier : si la clé est "content" et qu'elle possède une propriété "image".
1822
- if (key === "content" && value && typeof value === "object" && value.image) {
1823
- value.image = this._normalizeImage(value.image);
1824
- }
1825
-
1826
- normalizedData[key] = value;
1827
- });
1828
-
1829
- // Normalisation des identifiants après traitement de l'objet.
1830
- return this._normalizeId(normalizedData);
1831
- }
1832
-
1833
- // Pour les autres types (nombres, booléens déjà, etc.), on renvoie la donnée inchangée.
1834
- return data;
1835
- }
1836
-
1837
- private _startBeforeEndValidate = (schema: any, data: any) => {
1838
- if (!data.startDate || !data.endDate) return true;
1839
-
1840
- const isValid = new Date(data.startDate) < new Date(data.endDate);
1841
- if (!isValid) {
1842
- // @ts-expect-error - on sait que ValidateFunction expose .errors au runtime
1843
- this._startBeforeEndValidate.errors = [
1844
- {
1845
- instancePath: "/startDate", // ou "." si tu veux le chemin actuel
1846
- schemaPath: "#/startBeforeEnd",
1847
- keyword: "startBeforeEnd",
1848
- params: {},
1849
- message: "startDate must be before endDate"
1850
- }
1851
- ];
1852
- }
1853
- return isValid;
1854
- };
1855
-
1856
-
1857
- /**
1858
- * Récupère le schéma de requête pour un endpoint donné.
1859
- * @param constant - Le nom de l'endpoint.
1860
- * @returns Le schéma de requête ou null si non trouvé.
1861
- */
1862
- getRequestSchema(constant: string) {
1863
- const endpoint = this._endpoints.find(e => e.constant === constant);
1864
- return endpoint?.request || null;
1865
- }
1866
-
1867
- /**
1868
- * Récupère le schéma de chemin pour un endpoint donné.
1869
- * @param constant - Le nom de l'endpoint.
1870
- * @returns Le schéma de chemin ou null si non trouvé.
1871
- */
1872
- getPathSchema(constant: string) {
1873
- return this._endpoints.find(e => e.constant === constant)?.pathParams || null;
1874
- }
1875
-
1876
- /**
1877
- * Permet d'écouter facilement un ensemble d'événements importants émis par l'ApiClient.
1878
- *
1879
- * @param handlers - Un objet avec des fonctions à appeler selon l'événement.
1880
- * @param [handlers.retryAttempt] - Lors d'une tentative de retry axios.
1881
- * @param [handlers.queuedOffline] - Lorsqu'une requête est mise en file.
1882
- * @param [handlers.circuitBreakerOpen] - Quand le breaker s'ouvre.
1883
- * @param [handlers.circuitBreakerReset] - Quand le breaker se referme.
1884
- * @param [handlers.refreshSuccess] - Quand le token est rafraîchi.
1885
- * @param [handlers.refreshFailed] - Quand le refresh échoue.
1886
- * @param [handlers.sessionReset] - Quand la session est réinitialisée.
1887
- * @param [handlers.validationError] - Quand une validation échoue.
1888
- * @param [handlers.offlineModeChanged] - Quand le mode offline change.
1889
- * @param [handlers.userLoggedIn] - Quand un utilisateur se connecte.
1890
- */
1891
- onEvent(handlers: Record<string, (...args: any[]) => void> = {}) {
1892
- const availableEvents = [
1893
- "retryAttempt",
1894
- "queuedOffline",
1895
- "circuitBreakerOpen",
1896
- "circuitBreakerReset",
1897
- "refreshSuccess",
1898
- "refreshFailed",
1899
- "sessionReset",
1900
- "validationError",
1901
- "offlineModeChanged",
1902
- "userLoggedIn"
1903
- ];
1904
-
1905
- for (const eventName of availableEvents) {
1906
- if (typeof handlers[eventName] === "function") {
1907
- this.on(eventName, handlers[eventName] as (...args: any[]) => void);
1908
- }
1909
- }
1910
- }
1911
-
1912
- /**
1913
- * Retourne la liste des noms d'événements personnalisés déclarés dans les endpoints.
1914
- * Utile pour introspection ou documentation.
1915
- *
1916
- * @returns Liste des événements émis dynamiquement via postActions.
1917
- */
1918
- getDeclaredEvents() {
1919
- const events = new Set();
1920
- for (const endpoint of this._endpoints) {
1921
- if (Array.isArray(endpoint.postActions)) {
1922
- for (const action of endpoint.postActions) {
1923
- if (action.type === "emitEvent" && action.event) {
1924
- events.add(action.event);
1925
- }
1926
- }
1927
- }
1928
- }
1929
- return Array.from(events);
1930
- }
1931
-
1932
- }
1933
-
1934
- /**
1935
- * @event ApiClient#retryAttempt
1936
- * @type {Object}
1937
- * @property {number} retryCount - Le numéro de tentative.
1938
- * @property {string} url - L'URL de la requête ayant échoué.
1939
- */
1940
-
1941
- /**
1942
- * @event ApiClient#queuedOffline
1943
- * @type {Object}
1944
- * @property {string} constant - Le nom de l'endpoint.
1945
- * @property {Object} data - Les données envoyées.
1946
- * @property {"offlineMode"|"circuitBreaker"} reason - La raison de la mise en file.
1947
- */
1948
-
1949
- /**
1950
- * @event ApiClient#circuitBreakerOpen
1951
- * @type {Object}
1952
- * @property {number} timestamp - Date (ms) à laquelle le breaker s'est ouvert.
1953
- */
1954
-
1955
- /**
1956
- * @event ApiClient#circuitBreakerReset
1957
- * @type {void}
1958
- */
1959
-
1960
- /**
1961
- * @event ApiClient#refreshSuccess
1962
- * @type {Object}
1963
- * @property {string} token - Le nouveau token d'accès.
1964
- * @property {string} [refreshToken] - Le nouveau refreshToken (optionnel).
1965
- */
1966
-
1967
- /**
1968
- * @event ApiClient#refreshFailed
1969
- * @type {Object}
1970
- * @property {string} error - Le message d’erreur de refresh.
1971
- */
1972
-
1973
- /**
1974
- * @event ApiClient#sessionReset
1975
- * @type {void}
1976
- */
1977
-
1978
- /**
1979
- * @event ApiClient#validationError
1980
- * @type {Object}
1981
- * @property {"pathParams"|"request"|"response"} stage - Étape de validation échouée.
1982
- * @property {Array<Object>} errors - Erreurs AJV brutes.
1983
- */
1984
-
1985
- /**
1986
- * @event ApiClient#offlineModeChanged
1987
- * @type {boolean} - true si le client est offline, false sinon.
1988
- */
1989
-
1990
- /**
1991
- * @event ApiClient#userLoggedIn
1992
- * @param user - Les données utilisateur extraites de la réponse.
1993
- */
1994
-