@everysk/apps-sdk 0.2.0 → 0.3.0-beta-20261019175049

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 (378) hide show
  1. package/README.md +342 -12
  2. package/dist/EveryskApp.d.ts +34 -2
  3. package/dist/EveryskApp.d.ts.map +1 -1
  4. package/dist/EveryskApp.js +35 -2
  5. package/dist/EveryskApp.js.map +1 -1
  6. package/dist/agGrid/handleRowsScrollEnd.d.ts +53 -0
  7. package/dist/agGrid/handleRowsScrollEnd.d.ts.map +1 -0
  8. package/dist/agGrid/handleRowsScrollEnd.js +47 -0
  9. package/dist/agGrid/handleRowsScrollEnd.js.map +1 -0
  10. package/dist/agGrid/index.d.ts +2 -0
  11. package/dist/agGrid/index.d.ts.map +1 -1
  12. package/dist/agGrid/index.js +2 -1
  13. package/dist/api/datastore.d.ts +10 -8
  14. package/dist/api/datastore.d.ts.map +1 -1
  15. package/dist/api/datastore.js +24 -55
  16. package/dist/api/datastore.js.map +1 -1
  17. package/dist/api/entityQuery.d.ts +46 -41
  18. package/dist/api/entityQuery.d.ts.map +1 -1
  19. package/dist/api/entityQuery.js +55 -14
  20. package/dist/api/entityQuery.js.map +1 -1
  21. package/dist/api/file.d.ts +26 -9
  22. package/dist/api/file.d.ts.map +1 -1
  23. package/dist/api/file.js +45 -55
  24. package/dist/api/file.js.map +1 -1
  25. package/dist/api/portfolio.d.ts +11 -8
  26. package/dist/api/portfolio.d.ts.map +1 -1
  27. package/dist/api/portfolio.js +24 -55
  28. package/dist/api/portfolio.js.map +1 -1
  29. package/dist/api/profile.d.ts +3 -2
  30. package/dist/api/profile.d.ts.map +1 -1
  31. package/dist/api/profile.js +4 -9
  32. package/dist/api/profile.js.map +1 -1
  33. package/dist/api/request.d.ts +106 -0
  34. package/dist/api/request.d.ts.map +1 -0
  35. package/dist/api/request.js +132 -0
  36. package/dist/api/request.js.map +1 -0
  37. package/dist/api/websocketsConfig.d.ts +7 -2
  38. package/dist/api/websocketsConfig.d.ts.map +1 -1
  39. package/dist/api/websocketsConfig.js +2 -10
  40. package/dist/api/websocketsConfig.js.map +1 -1
  41. package/dist/api/workflow.d.ts +84 -89
  42. package/dist/api/workflow.d.ts.map +1 -1
  43. package/dist/api/workflow.js +121 -208
  44. package/dist/api/workflow.js.map +1 -1
  45. package/dist/api/workspace.d.ts +18 -8
  46. package/dist/api/workspace.d.ts.map +1 -1
  47. package/dist/api/workspace.js +21 -26
  48. package/dist/api/workspace.js.map +1 -1
  49. package/dist/components/ui/Footer/index.d.ts.map +1 -1
  50. package/dist/components/ui/Footer/index.js.map +1 -1
  51. package/dist/components/ui/GlobalLoading/index.d.ts.map +1 -1
  52. package/dist/components/ui/GlobalLoading/index.js.map +1 -1
  53. package/dist/components/ui/PageContainer/index.d.ts +1 -1
  54. package/dist/components/ui/PageContainer/index.d.ts.map +1 -1
  55. package/dist/components/ui/PageContainer/index.js.map +1 -1
  56. package/dist/components/ui/Toolbar/index.d.ts +40 -3
  57. package/dist/components/ui/Toolbar/index.d.ts.map +1 -1
  58. package/dist/components/ui/Toolbar/index.js +42 -15
  59. package/dist/components/ui/Toolbar/index.js.map +1 -1
  60. package/dist/contexts/appAlertContext/provider.js.map +1 -1
  61. package/dist/contexts/appAlertContext/types.d.ts.map +1 -1
  62. package/dist/contexts/appConfigContext/index.d.ts.map +1 -1
  63. package/dist/contexts/appConfigContext/index.js.map +1 -1
  64. package/dist/contexts/appConfigContext/provider.d.ts.map +1 -1
  65. package/dist/contexts/appConfigContext/provider.js.map +1 -1
  66. package/dist/contexts/webSocketContext/handler.d.ts +3 -2
  67. package/dist/contexts/webSocketContext/handler.d.ts.map +1 -1
  68. package/dist/contexts/webSocketContext/handler.js.map +1 -1
  69. package/dist/contexts/webSocketContext/hooks/useWebSocket/index.d.ts +1 -1
  70. package/dist/contexts/webSocketContext/hooks/useWebSocket/index.js +1 -1
  71. package/dist/contexts/webSocketContext/hooks/useWebSocket/index.js.map +1 -1
  72. package/dist/contexts/webSocketContext/provider.d.ts +6 -3
  73. package/dist/contexts/webSocketContext/provider.d.ts.map +1 -1
  74. package/dist/contexts/webSocketContext/provider.js +42 -7
  75. package/dist/contexts/webSocketContext/provider.js.map +1 -1
  76. package/dist/contexts/webSocketContext/types.d.ts +7 -5
  77. package/dist/contexts/webSocketContext/types.d.ts.map +1 -1
  78. package/dist/file/index.d.ts +2 -0
  79. package/dist/file/index.d.ts.map +1 -1
  80. package/dist/hooks/datastore/useDatastoreMutations.d.ts +4 -4
  81. package/dist/hooks/datastore/useDatastoreMutations.d.ts.map +1 -1
  82. package/dist/hooks/datastore/useDatastoreMutations.js +17 -55
  83. package/dist/hooks/datastore/useDatastoreMutations.js.map +1 -1
  84. package/dist/hooks/datastore/useFetchDatastore.d.ts +30 -41
  85. package/dist/hooks/datastore/useFetchDatastore.d.ts.map +1 -1
  86. package/dist/hooks/datastore/useFetchDatastore.js +25 -59
  87. package/dist/hooks/datastore/useFetchDatastore.js.map +1 -1
  88. package/dist/hooks/datastore/useFetchDatastores.d.ts +47 -52
  89. package/dist/hooks/datastore/useFetchDatastores.d.ts.map +1 -1
  90. package/dist/hooks/datastore/useFetchDatastores.js +55 -70
  91. package/dist/hooks/datastore/useFetchDatastores.js.map +1 -1
  92. package/dist/hooks/file/fileData.d.ts +17 -0
  93. package/dist/hooks/file/fileData.d.ts.map +1 -0
  94. package/dist/hooks/file/fileData.js +10 -0
  95. package/dist/hooks/file/fileData.js.map +1 -0
  96. package/dist/hooks/file/useFetchFile.d.ts +38 -58
  97. package/dist/hooks/file/useFetchFile.d.ts.map +1 -1
  98. package/dist/hooks/file/useFetchFile.js +26 -66
  99. package/dist/hooks/file/useFetchFile.js.map +1 -1
  100. package/dist/hooks/file/useFetchFiles.d.ts +18 -52
  101. package/dist/hooks/file/useFetchFiles.d.ts.map +1 -1
  102. package/dist/hooks/file/useFetchFiles.js +53 -69
  103. package/dist/hooks/file/useFetchFiles.js.map +1 -1
  104. package/dist/hooks/file/useFileMutations.d.ts +9 -8
  105. package/dist/hooks/file/useFileMutations.d.ts.map +1 -1
  106. package/dist/hooks/file/useFileMutations.js +23 -60
  107. package/dist/hooks/file/useFileMutations.js.map +1 -1
  108. package/dist/hooks/portfolio/useFetchPortfolio.d.ts +16 -50
  109. package/dist/hooks/portfolio/useFetchPortfolio.d.ts.map +1 -1
  110. package/dist/hooks/portfolio/useFetchPortfolio.js +15 -63
  111. package/dist/hooks/portfolio/useFetchPortfolio.js.map +1 -1
  112. package/dist/hooks/portfolio/useFetchPortfolios.d.ts +13 -52
  113. package/dist/hooks/portfolio/useFetchPortfolios.d.ts.map +1 -1
  114. package/dist/hooks/portfolio/useFetchPortfolios.js +29 -71
  115. package/dist/hooks/portfolio/useFetchPortfolios.js.map +1 -1
  116. package/dist/hooks/portfolio/usePortfolioMutations.d.ts +8 -8
  117. package/dist/hooks/portfolio/usePortfolioMutations.d.ts.map +1 -1
  118. package/dist/hooks/portfolio/usePortfolioMutations.js +20 -58
  119. package/dist/hooks/portfolio/usePortfolioMutations.js.map +1 -1
  120. package/dist/hooks/profile/useFetchProfile.d.ts +18 -44
  121. package/dist/hooks/profile/useFetchProfile.d.ts.map +1 -1
  122. package/dist/hooks/profile/useFetchProfile.js +16 -48
  123. package/dist/hooks/profile/useFetchProfile.js.map +1 -1
  124. package/dist/hooks/useAxios.js.map +1 -1
  125. package/dist/hooks/useEntityInfiniteQuery.d.ts +88 -0
  126. package/dist/hooks/useEntityInfiniteQuery.d.ts.map +1 -0
  127. package/dist/hooks/useEntityInfiniteQuery.js +111 -0
  128. package/dist/hooks/useEntityInfiniteQuery.js.map +1 -0
  129. package/dist/hooks/useEntityMutation.d.ts +47 -0
  130. package/dist/hooks/useEntityMutation.d.ts.map +1 -0
  131. package/dist/hooks/useEntityMutation.js +58 -0
  132. package/dist/hooks/useEntityMutation.js.map +1 -0
  133. package/dist/hooks/useEntityQuery.d.ts +68 -0
  134. package/dist/hooks/useEntityQuery.d.ts.map +1 -0
  135. package/dist/hooks/useEntityQuery.js +75 -0
  136. package/dist/hooks/useEntityQuery.js.map +1 -0
  137. package/dist/hooks/workflow/useFetchWorkerExecution.d.ts +17 -34
  138. package/dist/hooks/workflow/useFetchWorkerExecution.d.ts.map +1 -1
  139. package/dist/hooks/workflow/useFetchWorkerExecution.js +16 -46
  140. package/dist/hooks/workflow/useFetchWorkerExecution.js.map +1 -1
  141. package/dist/hooks/workflow/useFetchWorkerExecutions.d.ts +179 -41
  142. package/dist/hooks/workflow/useFetchWorkerExecutions.d.ts.map +1 -1
  143. package/dist/hooks/workflow/useFetchWorkerExecutions.js +29 -62
  144. package/dist/hooks/workflow/useFetchWorkerExecutions.js.map +1 -1
  145. package/dist/hooks/workflow/useFetchWorkflow.d.ts +16 -35
  146. package/dist/hooks/workflow/useFetchWorkflow.d.ts.map +1 -1
  147. package/dist/hooks/workflow/useFetchWorkflow.js +15 -48
  148. package/dist/hooks/workflow/useFetchWorkflow.js.map +1 -1
  149. package/dist/hooks/workflow/useFetchWorkflowExecution.d.ts +17 -34
  150. package/dist/hooks/workflow/useFetchWorkflowExecution.d.ts.map +1 -1
  151. package/dist/hooks/workflow/useFetchWorkflowExecution.js +16 -46
  152. package/dist/hooks/workflow/useFetchWorkflowExecution.js.map +1 -1
  153. package/dist/hooks/workflow/useFetchWorkflowExecutions.d.ts +22 -36
  154. package/dist/hooks/workflow/useFetchWorkflowExecutions.d.ts.map +1 -1
  155. package/dist/hooks/workflow/useFetchWorkflowExecutions.js +43 -56
  156. package/dist/hooks/workflow/useFetchWorkflowExecutions.js.map +1 -1
  157. package/dist/hooks/workflow/useFetchWorkflows.d.ts +20 -37
  158. package/dist/hooks/workflow/useFetchWorkflows.d.ts.map +1 -1
  159. package/dist/hooks/workflow/useFetchWorkflows.js +32 -47
  160. package/dist/hooks/workflow/useFetchWorkflows.js.map +1 -1
  161. package/dist/hooks/workflow/useRunWorkflow.d.ts +21 -16
  162. package/dist/hooks/workflow/useRunWorkflow.d.ts.map +1 -1
  163. package/dist/hooks/workflow/useRunWorkflow.js +66 -29
  164. package/dist/hooks/workflow/useRunWorkflow.js.map +1 -1
  165. package/dist/hooks/workflow/useTriggerWorkflow.d.ts +20 -0
  166. package/dist/hooks/workflow/useTriggerWorkflow.d.ts.map +1 -0
  167. package/dist/hooks/workflow/useTriggerWorkflow.js +32 -0
  168. package/dist/hooks/workflow/useTriggerWorkflow.js.map +1 -0
  169. package/dist/hooks/workspace/useFetchWorkspace.d.ts +19 -34
  170. package/dist/hooks/workspace/useFetchWorkspace.d.ts.map +1 -1
  171. package/dist/hooks/workspace/useFetchWorkspace.js +16 -46
  172. package/dist/hooks/workspace/useFetchWorkspace.js.map +1 -1
  173. package/dist/hooks/workspace/useFetchWorkspaces.d.ts +17 -37
  174. package/dist/hooks/workspace/useFetchWorkspaces.d.ts.map +1 -1
  175. package/dist/hooks/workspace/useFetchWorkspaces.js +25 -47
  176. package/dist/hooks/workspace/useFetchWorkspaces.js.map +1 -1
  177. package/dist/index.d.ts +13 -10
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +13 -11
  180. package/dist/lib/abort.d.ts +58 -0
  181. package/dist/lib/abort.d.ts.map +1 -0
  182. package/dist/lib/abort.js +112 -0
  183. package/dist/lib/abort.js.map +1 -0
  184. package/dist/lib/datastoreTable.d.ts +59 -54
  185. package/dist/lib/datastoreTable.d.ts.map +1 -1
  186. package/dist/lib/datastoreTable.js +58 -62
  187. package/dist/lib/datastoreTable.js.map +1 -1
  188. package/dist/lib/file.d.ts +34 -0
  189. package/dist/lib/file.d.ts.map +1 -0
  190. package/dist/lib/file.js +63 -0
  191. package/dist/lib/file.js.map +1 -0
  192. package/dist/lib/queryClient.d.ts.map +1 -1
  193. package/dist/lib/queryClient.js.map +1 -1
  194. package/dist/lib/workflowRun.d.ts +91 -0
  195. package/dist/lib/workflowRun.d.ts.map +1 -0
  196. package/dist/lib/workflowRun.js +224 -0
  197. package/dist/lib/workflowRun.js.map +1 -0
  198. package/dist/socket/index.d.ts +1 -3
  199. package/dist/socket/index.d.ts.map +1 -1
  200. package/dist/socket/index.js +3 -5
  201. package/dist/test/mocks/index.d.ts +1 -1
  202. package/dist/test/mocks/index.d.ts.map +1 -1
  203. package/dist/test/msw/fakeApi.d.ts +43 -0
  204. package/dist/test/msw/fakeApi.d.ts.map +1 -0
  205. package/dist/test/msw/fakeApi.js +74 -0
  206. package/dist/test/msw/fakeApi.js.map +1 -0
  207. package/dist/test/msw/handlers.d.ts +20 -0
  208. package/dist/test/msw/handlers.d.ts.map +1 -0
  209. package/dist/test/msw/handlers.js +279 -0
  210. package/dist/test/msw/handlers.js.map +1 -0
  211. package/dist/test/msw/index.d.ts +5 -0
  212. package/dist/test/msw/index.d.ts.map +1 -0
  213. package/dist/test/msw/index.js +2 -0
  214. package/dist/test/msw/query.d.ts +34 -0
  215. package/dist/test/msw/query.d.ts.map +1 -0
  216. package/dist/test/msw/query.js +96 -0
  217. package/dist/test/msw/query.js.map +1 -0
  218. package/dist/test/msw/recorder.d.ts +30 -0
  219. package/dist/test/msw/recorder.d.ts.map +1 -0
  220. package/dist/test/msw/recorder.js +65 -0
  221. package/dist/test/msw/recorder.js.map +1 -0
  222. package/dist/test/msw/store.d.ts +57 -0
  223. package/dist/test/msw/store.d.ts.map +1 -0
  224. package/dist/test/msw/store.js +145 -0
  225. package/dist/test/msw/store.js.map +1 -0
  226. package/dist/test/renderWithProviders.d.ts +2 -2
  227. package/dist/test/renderWithProviders.d.ts.map +1 -1
  228. package/dist/test/renderWithProviders.js.map +1 -1
  229. package/dist/test/setup.js.map +1 -1
  230. package/dist/theme/index.d.ts +1 -1
  231. package/dist/theme/index.d.ts.map +1 -1
  232. package/dist/theme/overrides/index.d.ts +4 -2
  233. package/dist/theme/overrides/index.d.ts.map +1 -1
  234. package/dist/theme/overrides/index.js +5 -3
  235. package/dist/theme/overrides/index.js.map +1 -1
  236. package/dist/theme/overrides/muiButton.d.ts +2 -2
  237. package/dist/theme/overrides/muiButton.js.map +1 -1
  238. package/dist/theme/overrides/muiCssBaseline.d.ts.map +1 -1
  239. package/dist/theme/overrides/muiCssBaseline.js.map +1 -1
  240. package/dist/theme/overrides/muiDatePicker.js.map +1 -1
  241. package/dist/theme/overrides/muiFormControl.js.map +1 -1
  242. package/dist/theme/overrides/muiIconButton.d.ts +2 -2
  243. package/dist/theme/overrides/muiIconButton.js.map +1 -1
  244. package/dist/theme/overrides/muiSelect.d.ts.map +1 -1
  245. package/dist/theme/overrides/muiSelect.js.map +1 -1
  246. package/dist/theme/overrides/muiSvgIcon.d.ts.map +1 -1
  247. package/dist/theme/overrides/muiSvgIcon.js.map +1 -1
  248. package/dist/theme/overrides/muiTextField.js.map +1 -1
  249. package/dist/theme/overrides/muiToggleButton.d.ts +2 -2
  250. package/dist/theme/overrides/muiToggleButton.js.map +1 -1
  251. package/dist/theme/overrides/muiToggleButtonGroup.d.ts +2 -2
  252. package/dist/theme/overrides/muiToggleButtonGroup.js.map +1 -1
  253. package/dist/theme/themes/DefaultDark.js +2 -2
  254. package/dist/theme/themes/DefaultDark.js.map +1 -1
  255. package/dist/theme/themes/DefaultLight.js +2 -2
  256. package/dist/theme/themes/DefaultLight.js.map +1 -1
  257. package/dist/theme/themes/EveryskDark.js +2 -2
  258. package/dist/theme/themes/EveryskDark.js.map +1 -1
  259. package/dist/theme/themes/EveryskLight.js +2 -2
  260. package/dist/theme/themes/EveryskLight.js.map +1 -1
  261. package/dist/theme/themes/MidnightEmber.js +2 -2
  262. package/dist/theme/themes/MidnightEmber.js.map +1 -1
  263. package/dist/types/anyRecord.d.ts.map +1 -1
  264. package/dist/types/datastore.d.ts +35 -5
  265. package/dist/types/datastore.d.ts.map +1 -1
  266. package/dist/types/datastoreDataFormat.d.ts +13 -0
  267. package/dist/types/datastoreDataFormat.d.ts.map +1 -0
  268. package/dist/types/entityQuery.d.ts +86 -16
  269. package/dist/types/entityQuery.d.ts.map +1 -1
  270. package/dist/types/file.d.ts +7 -3
  271. package/dist/types/file.d.ts.map +1 -1
  272. package/dist/types/portfolio.d.ts +1 -3
  273. package/dist/types/portfolio.d.ts.map +1 -1
  274. package/dist/types/profile.d.ts +0 -10
  275. package/dist/types/profile.d.ts.map +1 -1
  276. package/dist/types/workflow.d.ts +35 -6
  277. package/dist/types/workflow.d.ts.map +1 -1
  278. package/dist/types/workspace.d.ts +1 -2
  279. package/dist/types/workspace.d.ts.map +1 -1
  280. package/dist/ui/index.d.ts +1 -1
  281. package/dist/ui/index.d.ts.map +1 -1
  282. package/dist/workflow/index.d.ts +2 -0
  283. package/dist/workflow/index.d.ts.map +1 -1
  284. package/package.json +10 -1
  285. package/src/EveryskApp.tsx +53 -2
  286. package/src/agGrid/handleRowsScrollEnd.ts +71 -0
  287. package/src/agGrid/index.ts +5 -1
  288. package/src/api/datastore.ts +79 -107
  289. package/src/api/entityQuery.ts +86 -17
  290. package/src/api/file.ts +110 -103
  291. package/src/api/portfolio.ts +82 -125
  292. package/src/api/profile.ts +25 -18
  293. package/src/api/request.ts +168 -0
  294. package/src/api/websocketsConfig.ts +11 -19
  295. package/src/api/workflow.ts +232 -334
  296. package/src/api/workspace.ts +43 -43
  297. package/src/components/ui/Footer/index.tsx +8 -7
  298. package/src/components/ui/GlobalLoading/index.tsx +13 -3
  299. package/src/components/ui/PageContainer/index.tsx +9 -2
  300. package/src/components/ui/Toolbar/index.tsx +105 -30
  301. package/src/contexts/appAlertContext/provider.tsx +1 -1
  302. package/src/contexts/appAlertContext/types.ts +3 -3
  303. package/src/contexts/appConfigContext/index.tsx +1 -1
  304. package/src/contexts/appConfigContext/provider.tsx +1 -5
  305. package/src/contexts/webSocketContext/handler.ts +8 -2
  306. package/src/contexts/webSocketContext/hooks/useWebSocket/index.ts +1 -1
  307. package/src/contexts/webSocketContext/provider.tsx +64 -14
  308. package/src/contexts/webSocketContext/types.ts +7 -6
  309. package/src/file/index.ts +2 -0
  310. package/src/hooks/datastore/useDatastoreMutations.ts +19 -60
  311. package/src/hooks/datastore/useFetchDatastore.ts +45 -73
  312. package/src/hooks/datastore/useFetchDatastores.ts +106 -131
  313. package/src/hooks/file/fileData.ts +19 -0
  314. package/src/hooks/file/useFetchFile.ts +41 -76
  315. package/src/hooks/file/useFetchFiles.ts +92 -87
  316. package/src/hooks/file/useFileMutations.ts +23 -73
  317. package/src/hooks/portfolio/useFetchPortfolio.ts +26 -78
  318. package/src/hooks/portfolio/useFetchPortfolios.ts +50 -93
  319. package/src/hooks/portfolio/usePortfolioMutations.ts +22 -72
  320. package/src/hooks/profile/useFetchProfile.ts +24 -65
  321. package/src/hooks/useAxios.ts +5 -5
  322. package/src/hooks/useEntityInfiniteQuery.ts +193 -0
  323. package/src/hooks/useEntityMutation.ts +70 -0
  324. package/src/hooks/useEntityQuery.ts +117 -0
  325. package/src/hooks/workflow/useFetchWorkerExecution.ts +27 -56
  326. package/src/hooks/workflow/useFetchWorkerExecutions.ts +54 -79
  327. package/src/hooks/workflow/useFetchWorkflow.ts +24 -58
  328. package/src/hooks/workflow/useFetchWorkflowExecution.ts +27 -57
  329. package/src/hooks/workflow/useFetchWorkflowExecutions.ts +64 -73
  330. package/src/hooks/workflow/useFetchWorkflows.ts +48 -64
  331. package/src/hooks/workflow/useRunWorkflow.ts +82 -36
  332. package/src/hooks/workflow/useTriggerWorkflow.ts +36 -0
  333. package/src/hooks/workspace/useFetchWorkspace.ts +25 -58
  334. package/src/hooks/workspace/useFetchWorkspaces.ts +38 -69
  335. package/src/index.ts +34 -19
  336. package/src/lib/abort.ts +131 -0
  337. package/src/lib/datastoreTable.ts +127 -74
  338. package/src/lib/file.ts +67 -0
  339. package/src/lib/queryClient.ts +11 -13
  340. package/src/lib/workflowRun.ts +329 -0
  341. package/src/socket/index.ts +1 -3
  342. package/src/test/mocks/index.ts +1 -5
  343. package/src/test/msw/fakeApi.ts +83 -0
  344. package/src/test/msw/handlers.ts +350 -0
  345. package/src/test/msw/index.ts +9 -0
  346. package/src/test/msw/query.ts +134 -0
  347. package/src/test/msw/recorder.ts +88 -0
  348. package/src/test/msw/store.ts +182 -0
  349. package/src/test/renderWithProviders.tsx +8 -3
  350. package/src/test/setup.ts +16 -16
  351. package/src/theme/index.ts +1 -1
  352. package/src/theme/overrides/index.ts +5 -3
  353. package/src/theme/overrides/muiButton.ts +3 -3
  354. package/src/theme/overrides/muiCssBaseline.ts +11 -12
  355. package/src/theme/overrides/muiDatePicker.ts +2 -2
  356. package/src/theme/overrides/muiFormControl.ts +2 -2
  357. package/src/theme/overrides/muiIconButton.ts +3 -3
  358. package/src/theme/overrides/muiSelect.ts +2 -2
  359. package/src/theme/overrides/muiSvgIcon.ts +2 -2
  360. package/src/theme/overrides/muiTextField.ts +1 -1
  361. package/src/theme/overrides/muiToggleButton.ts +4 -4
  362. package/src/theme/overrides/muiToggleButtonGroup.ts +4 -4
  363. package/src/theme/themes/DefaultDark.ts +2 -2
  364. package/src/theme/themes/DefaultLight.ts +2 -2
  365. package/src/theme/themes/EveryskDark.ts +2 -2
  366. package/src/theme/themes/EveryskLight.ts +2 -2
  367. package/src/theme/themes/MidnightEmber.ts +15 -15
  368. package/src/types/anyRecord.ts +2 -2
  369. package/src/types/datastore.ts +56 -22
  370. package/src/types/datastoreDataFormat.ts +12 -0
  371. package/src/types/entityQuery.ts +122 -38
  372. package/src/types/file.ts +8 -6
  373. package/src/types/portfolio.ts +70 -74
  374. package/src/types/profile.ts +0 -11
  375. package/src/types/workflow.ts +38 -9
  376. package/src/types/workspace.ts +2 -4
  377. package/src/ui/index.ts +1 -1
  378. package/src/workflow/index.ts +5 -0
@@ -0,0 +1,350 @@
1
+ import { http, HttpResponse, type HttpHandler, type JsonBodyType } from "msw";
2
+
3
+ import type { AnyRecord } from "@/types/anyRecord";
4
+ import type { WorkerExecution } from "@/types/workflow";
5
+
6
+ import { applyListQuery, applyTextSearch, QueryError } from "./query";
7
+ import { parsedQuery, pathOf } from "./recorder";
8
+ import { complete, newId, nowSeconds, type FakeApiStore } from "./store";
9
+
10
+ export type Failure = { method: string; path: string; status: number; message: string };
11
+
12
+ /** What the handlers read and write: the store, and the failures queued. */
13
+ export type FakeState = {
14
+ store: () => FakeApiStore;
15
+ takeFailure: (method: string, path: string) => Failure | undefined;
16
+ };
17
+
18
+ type Answer = { status?: number; json: JsonBodyType };
19
+
20
+ type Context = { url: URL; path: string; segments: string[]; body: unknown };
21
+
22
+ /** The page size a text-query list answers with when none is given. Source: `ConfigDefaults.py`. */
23
+ const TEXT_PAGE_SIZE = 10;
24
+
25
+ export const errorBody = (status: number, message: string) => ({ code: status, message });
26
+
27
+ const notFound = (entity: string, id: string): Answer => ({
28
+ status: 404,
29
+ json: errorBody(404, `${entity} ${id} not found`),
30
+ });
31
+
32
+ /**
33
+ * Wraps a responder: answers with a failure the test queued for the request,
34
+ * with the API's error body when the responder throws a `QueryError`, or with what it returns.
35
+ */
36
+ const answering =
37
+ (state: FakeState, respond: (context: Context) => Answer) =>
38
+ async ({ request }: { request: Request }) => {
39
+ const url = new URL(request.url);
40
+ const path = pathOf(url);
41
+ const method = request.method.toUpperCase();
42
+ const body = ["POST", "PUT"].includes(method)
43
+ ? await request.json().catch(() => undefined)
44
+ : undefined;
45
+
46
+ const failure = state.takeFailure(method, path);
47
+ let answer: Answer;
48
+ if (failure) {
49
+ answer = { status: failure.status, json: errorBody(failure.status, failure.message) };
50
+ } else {
51
+ try {
52
+ answer = respond({ url, path, segments: path.split("/").filter(Boolean), body });
53
+ } catch (error) {
54
+ if (!(error instanceof QueryError)) throw error;
55
+ answer = { status: error.status, json: errorBody(error.status, error.message) };
56
+ }
57
+ }
58
+ return HttpResponse.json(answer.json, { status: answer.status ?? 200 });
59
+ };
60
+
61
+ /** A page of a text-query list: the URL's `page_size` (10 by default) and `page_token`. */
62
+ const textPage = (records: AnyRecord[], url: URL) => {
63
+ const offset = Number(url.searchParams.get("page_token") ?? 0);
64
+ const end = offset + Number(url.searchParams.get("page_size") ?? TEXT_PAGE_SIZE);
65
+ return {
66
+ page: records.slice(offset, end),
67
+ nextPageToken: end < records.length ? String(end) : null,
68
+ };
69
+ };
70
+
71
+ type JsonQueryEntity = {
72
+ /** The URL segment and the list's key: `datastores`. */
73
+ plural: "datastores" | "files" | "portfolios";
74
+ /** A single record's key: `datastore`. */
75
+ singular: string;
76
+ /** For error messages: `Datastore`. */
77
+ label: string;
78
+ create: (record: AnyRecord) => AnyRecord;
79
+ /** What a read by id answers, given the URL: a file leaves its data out on `with_data=false`. */
80
+ read?: (record: AnyRecord, url: URL) => AnyRecord;
81
+ };
82
+
83
+ /**
84
+ * The five routes of an entity listed by JSON query: list, read, create, update and delete,
85
+ * each answered from the store with the API's envelopes — the plural key with
86
+ * `next_page_token` for a list, the singular key for one record, and a delete by id answering
87
+ * one `{ id, name, deleted }`.
88
+ */
89
+ const jsonQueryEntity = (state: FakeState, entity: JsonQueryEntity): HttpHandler[] => {
90
+ const { plural, singular, label } = entity;
91
+ const records = () => state.store()[plural] as unknown as AnyRecord[];
92
+ const replace = (next: AnyRecord[]) => {
93
+ (state.store() as unknown as Record<string, AnyRecord[]>)[plural] = next;
94
+ };
95
+
96
+ return [
97
+ http.get(
98
+ `*/api/${plural}`,
99
+ answering(state, ({ url }) => {
100
+ const query = parsedQuery(url.searchParams.get("query"));
101
+ if (typeof query === "object") {
102
+ const { page, nextPageToken } = applyListQuery(records(), query, {
103
+ pageSize: url.searchParams.get("page_size"),
104
+ pageToken: url.searchParams.get("page_token"),
105
+ });
106
+ return { json: { [plural]: page, next_page_token: nextPageToken } };
107
+ }
108
+ const { page, nextPageToken } = textPage(
109
+ applyTextSearch(records(), query ?? ""),
110
+ url,
111
+ );
112
+ return { json: { [plural]: page, next_page_token: nextPageToken } };
113
+ }),
114
+ ),
115
+ http.get(
116
+ `*/api/${plural}/:id`,
117
+ answering(state, ({ url, segments }) => {
118
+ const id = segments[1] ?? "";
119
+ if (url.searchParams.get("workspace") === "") {
120
+ return { status: 403, json: errorBody(403, "An empty workspace is refused.") };
121
+ }
122
+ const found = records().find((r) => r.id === id);
123
+ if (!found) return notFound(label, id);
124
+ return { json: { [singular]: entity.read ? entity.read(found, url) : found } };
125
+ }),
126
+ ),
127
+ http.post(
128
+ `*/api/${plural}`,
129
+ answering(state, ({ body }) => {
130
+ const now = nowSeconds();
131
+ const created = entity.create({
132
+ ...(body as AnyRecord),
133
+ id: undefined,
134
+ created: now,
135
+ updated: now,
136
+ });
137
+ records().push(created);
138
+ return { json: { [singular]: created } };
139
+ }),
140
+ ),
141
+ http.put(
142
+ `*/api/${plural}/:id`,
143
+ answering(state, ({ segments, body }) => {
144
+ const id = segments[1] ?? "";
145
+ const index = records().findIndex((r) => r.id === id);
146
+ if (index === -1) return notFound(label, id);
147
+ const updated = {
148
+ ...records()[index],
149
+ ...(body as AnyRecord),
150
+ id,
151
+ updated: nowSeconds(),
152
+ };
153
+ records()[index] = updated;
154
+ return { json: { [singular]: updated } };
155
+ }),
156
+ ),
157
+ http.delete(
158
+ `*/api/${plural}/:id`,
159
+ answering(state, ({ segments }) => {
160
+ const id = segments[1] ?? "";
161
+ const found = records().find((r) => r.id === id);
162
+ if (!found) return notFound(label, id);
163
+ replace(records().filter((r) => r.id !== id));
164
+ return { json: { [singular]: { id, name: found.name, deleted: true } } };
165
+ }),
166
+ ),
167
+ ];
168
+ };
169
+
170
+ /**
171
+ * Worker executions as `GET /workflows/:id/worker_executions` groups them: one group per worker,
172
+ * in the order the workers first appear, a worker with several runs as a parallel group.
173
+ */
174
+ const groupWorkerExecutions = (executions: WorkerExecution[]) => {
175
+ const byWorker = new Map<string, WorkerExecution[]>();
176
+ for (const execution of executions) {
177
+ byWorker.set(execution.worker_id, [
178
+ ...(byWorker.get(execution.worker_id) ?? []),
179
+ execution,
180
+ ]);
181
+ }
182
+ const groups = [...byWorker.values()].map((runs) => {
183
+ const base = { worker_id: runs[0]!.worker_id, worker_name: runs[0]!.worker_name };
184
+ return runs.length > 1
185
+ ? { ...base, is_parallel_execution: true, executions: runs }
186
+ : { ...base, is_parallel_execution: false, execution: runs[0] };
187
+ });
188
+ const parallel = [...byWorker.values()].filter((runs) => runs.length > 1);
189
+ return {
190
+ worker_executions: groups,
191
+ parallel_index_max:
192
+ parallel.length > 0 ? Math.max(...parallel.map((r) => r.length - 1)) : -1,
193
+ };
194
+ };
195
+
196
+ const workflowHandlers = (state: FakeState): HttpHandler[] => [
197
+ // The workflow list takes only a text search, under the `workspace` it is given.
198
+ http.get(
199
+ "*/api/workflows",
200
+ answering(state, ({ url }) => {
201
+ const workspace = url.searchParams.get("workspace") ?? "main";
202
+ const inWorkspace = state.store().workflows.filter((w) => w.workspace === workspace);
203
+ const { page, nextPageToken } = textPage(
204
+ applyTextSearch(inWorkspace, url.searchParams.get("query") ?? ""),
205
+ url,
206
+ );
207
+ return { json: { workflows: page, next_page_token: nextPageToken } };
208
+ }),
209
+ ),
210
+ http.get(
211
+ "*/api/workflows/:id",
212
+ answering(state, ({ segments }) => {
213
+ const id = segments[1] ?? "";
214
+ const found = state.store().workflows.find((w) => w.id === id);
215
+ return found ? { json: { workflow: found } } : notFound("Workflow", id);
216
+ }),
217
+ ),
218
+ // 202 with the execution it started, still RUNNING, and a `result` that is always null.
219
+ http.post(
220
+ "*/api/workflows/:id/run",
221
+ answering(state, ({ segments, body }) => {
222
+ const id = segments[1] ?? "";
223
+ const workflow = state.store().workflows.find((w) => w.id === id);
224
+ if (!workflow) return notFound("Workflow", id);
225
+ const now = nowSeconds();
226
+ const execution = complete.workflowExecution({
227
+ id: newId("wfex"),
228
+ workflow_id: id,
229
+ workflow_name: workflow.name,
230
+ workspace: (body as AnyRecord | undefined)?.workspace ?? workflow.workspace,
231
+ status: "RUNNING",
232
+ run_status: "RUNNING",
233
+ started: now,
234
+ created: now,
235
+ updated: now,
236
+ });
237
+ state.store().workflowExecutions.push(execution);
238
+ return { status: 202, json: { workflow_execution: execution, result: null } };
239
+ }),
240
+ ),
241
+ // Ignores a JSON query: reads `workflow_execution_id`, `worker_id`, `page_size` and
242
+ // `page_token` from the URL. With `workflow_execution_id` it answers that one execution.
243
+ http.get(
244
+ "*/api/workflows/:id/workflow_executions",
245
+ answering(state, ({ url, segments }) => {
246
+ const workflowId = segments[1] ?? "";
247
+ const executions = state
248
+ .store()
249
+ .workflowExecutions.filter((e) => e.workflow_id === workflowId);
250
+ const executionId = url.searchParams.get("workflow_execution_id");
251
+ if (executionId) {
252
+ const found = executions.find((e) => e.id === executionId);
253
+ return found
254
+ ? { json: { workflow_execution: found } }
255
+ : notFound("Workflow execution", executionId);
256
+ }
257
+ const workerId = url.searchParams.get("worker_id");
258
+ const ran = workerId
259
+ ? executions.filter((e) =>
260
+ state
261
+ .store()
262
+ .workerExecutions.some(
263
+ (w) => w.workflow_execution_id === e.id && w.worker_id === workerId,
264
+ ),
265
+ )
266
+ : executions;
267
+ const { page, nextPageToken } = textPage(ran, url);
268
+ return { json: { workflow_executions: page, next_page_token: nextPageToken } };
269
+ }),
270
+ ),
271
+ // Not paginated. With `worker_execution_id` it answers that one run; with
272
+ // `workflow_execution_id`, every worker of that execution, grouped.
273
+ http.get(
274
+ "*/api/workflows/:id/worker_executions",
275
+ answering(state, ({ url, segments }) => {
276
+ const workflowId = segments[1] ?? "";
277
+ const runs = state.store().workerExecutions.filter((w) => w.workflow_id === workflowId);
278
+ const workerExecutionId = url.searchParams.get("worker_execution_id");
279
+ if (workerExecutionId) {
280
+ const found = runs.find((w) => w.id === workerExecutionId);
281
+ return found
282
+ ? { json: { worker_execution: found } }
283
+ : notFound("Worker execution", workerExecutionId);
284
+ }
285
+ const executionId = url.searchParams.get("workflow_execution_id");
286
+ return {
287
+ json: groupWorkerExecutions(
288
+ runs.filter((w) => w.workflow_execution_id === executionId),
289
+ ),
290
+ };
291
+ }),
292
+ ),
293
+ ];
294
+
295
+ const workspaceHandlers = (state: FakeState): HttpHandler[] => [
296
+ http.get(
297
+ "*/api/workspaces",
298
+ answering(state, ({ url }) => {
299
+ const { page, nextPageToken } = textPage(
300
+ applyTextSearch(state.store().workspaces, url.searchParams.get("query") ?? ""),
301
+ url,
302
+ );
303
+ return { json: { workspaces: page, next_page_token: nextPageToken } };
304
+ }),
305
+ ),
306
+ // A workspace is identified by its name.
307
+ http.get(
308
+ "*/api/workspaces/:name",
309
+ answering(state, ({ segments }) => {
310
+ const name = decodeURIComponent(segments[1] ?? "");
311
+ const found = state.store().workspaces.find((w) => w.name === name);
312
+ return found ? { json: { workspace: found } } : notFound("Workspace", name);
313
+ }),
314
+ ),
315
+ ];
316
+
317
+ /** Every route the SDK's request functions call. */
318
+ export const fakeApiHandlers = (state: FakeState): HttpHandler[] => [
319
+ ...jsonQueryEntity(state, {
320
+ plural: "datastores",
321
+ singular: "datastore",
322
+ label: "Datastore",
323
+ create: (r) => complete.datastore(r),
324
+ }),
325
+ ...jsonQueryEntity(state, {
326
+ plural: "files",
327
+ singular: "file",
328
+ label: "File",
329
+ create: (r) => complete.file({ ...r, hash: undefined, url: undefined }),
330
+ read: (file, url) =>
331
+ url.searchParams.get("with_data") === "false" ? { ...file, data: null } : file,
332
+ }),
333
+ ...jsonQueryEntity(state, {
334
+ plural: "portfolios",
335
+ singular: "portfolio",
336
+ label: "Portfolio",
337
+ create: (r) => complete.portfolio(r),
338
+ }),
339
+ ...workflowHandlers(state),
340
+ ...workspaceHandlers(state),
341
+ // An app-only route; the SDK reads only `profile` from it.
342
+ http.get(
343
+ "*/api/profile",
344
+ answering(state, () => ({ json: { status: "OK", profile: state.store().profile } })),
345
+ ),
346
+ http.get(
347
+ "*/api/websockets_config",
348
+ answering(state, () => ({ json: state.store().websocketsConfig })),
349
+ ),
350
+ ];
@@ -0,0 +1,9 @@
1
+ // `@everysk/apps-sdk/test/msw` — a fake of the API behind `/api`, for an app's tests.
2
+ //
3
+ // Its own subpath because `msw` is an optional peer: an app that only renders components with
4
+ // `@everysk/apps-sdk/test` does not need it installed.
5
+
6
+ export * from "./fakeApi";
7
+ export type { RecordedRequest } from "./recorder";
8
+ export type { FakeApiSeed, FakeApiStore } from "./store";
9
+ export type { ListQuery } from "./query";
@@ -0,0 +1,134 @@
1
+ import type { AnyRecord } from "@/types/anyRecord";
2
+ import type { FilterOperator } from "@/types/entityQuery";
3
+
4
+ /** The JSON query a list read sends in `?query=`, as `buildQueryObject` writes it. */
5
+ export type ListQuery = {
6
+ filters?: unknown[][];
7
+ order?: string[];
8
+ projection?: string | string[];
9
+ page_size?: number;
10
+ page_token?: string;
11
+ };
12
+
13
+ /** The page size a JSON-query list answers with when none is given. Source: `ConfigDefaults.py`. */
14
+ const DEFAULT_PAGE_SIZE = 20;
15
+
16
+ const compare = (a: unknown, b: unknown): number => {
17
+ if (a === b) return 0;
18
+ if (a === undefined || a === null) return -1;
19
+ if (b === undefined || b === null) return 1;
20
+ return (a as string | number) < (b as string | number) ? -1 : 1;
21
+ };
22
+
23
+ /**
24
+ * Each operator the SDK can send, as the API applies it. Keyed by `FilterOperator`, so an
25
+ * operator added to the SDK does not compile until the fake knows it, rather than failing inside
26
+ * an app's test.
27
+ */
28
+ const OPERATORS: Record<FilterOperator, (actual: unknown, value: unknown) => boolean> = {
29
+ "=": (actual, value) => actual === value,
30
+ "!=": (actual, value) => actual !== value,
31
+ ">": (actual, value) => compare(actual, value) > 0,
32
+ ">=": (actual, value) => compare(actual, value) >= 0,
33
+ "<": (actual, value) => compare(actual, value) < 0,
34
+ "<=": (actual, value) => compare(actual, value) <= 0,
35
+ IN: (actual, value) => Array.isArray(value) && value.includes(actual),
36
+ NOT_IN: (actual, value) => Array.isArray(value) && !value.includes(actual),
37
+ };
38
+
39
+ const isOperator = (op: unknown): op is FilterOperator =>
40
+ typeof op === "string" && Object.hasOwn(OPERATORS, op);
41
+
42
+ /** A filter tuple as the API reads it: two items are `=`, three name the operator. */
43
+ const matches = (record: AnyRecord, tuple: unknown[]): boolean => {
44
+ const [field, op, value] = tuple.length === 2 ? [tuple[0], "=", tuple[1]] : tuple;
45
+ if (!isOperator(op)) throw new QueryError(`Unknown filter operator: ${String(op)}`);
46
+
47
+ return OPERATORS[op](record[field as string], value);
48
+ };
49
+
50
+ /** A query the API refuses: the handler answers it with the API's error body and status. */
51
+ export class QueryError extends Error {
52
+ status: number;
53
+
54
+ constructor(message: string, status = 400) {
55
+ super(message);
56
+ this.name = "QueryError";
57
+ this.status = status;
58
+ }
59
+ }
60
+
61
+ const ordered = (records: AnyRecord[], order: string[] = []): AnyRecord[] =>
62
+ [...records].sort((a, b) => {
63
+ for (const key of order) {
64
+ const desc = key.startsWith("-");
65
+ const field = desc ? key.slice(1) : key;
66
+ const result = compare(a[field], b[field]);
67
+ if (result !== 0) return desc ? -result : result;
68
+ }
69
+ return 0;
70
+ });
71
+
72
+ /**
73
+ * One field or an array, never a comma-joined string, and never inclusions mixed with
74
+ * exclusions: the API answers both with a 500 (`_normalize_projection`).
75
+ */
76
+ const project = (records: AnyRecord[], projection?: string | string[]): AnyRecord[] => {
77
+ if (projection === undefined || projection === "") return records;
78
+ const fields = Array.isArray(projection) ? projection : [projection];
79
+ if (fields.some((f) => f.includes(","))) {
80
+ throw new QueryError("A projection is one field or an array, not a list in a string.", 500);
81
+ }
82
+ const excluded = fields.filter((f) => f.startsWith("-")).map((f) => f.slice(1));
83
+ if (excluded.length > 0 && excluded.length < fields.length) {
84
+ throw new QueryError("A projection cannot mix inclusions with exclusions.", 500);
85
+ }
86
+
87
+ return records.map((record) =>
88
+ excluded.length > 0
89
+ ? Object.fromEntries(Object.entries(record).filter(([k]) => !excluded.includes(k)))
90
+ : Object.fromEntries(
91
+ Object.entries(record).filter(([k]) => k === "id" || fields.includes(k)),
92
+ ),
93
+ );
94
+ };
95
+
96
+ /**
97
+ * applyListQuery — a JSON-query list read, as the API answers it: the query's filters (the
98
+ * `workspace` URL parameter is ignored on this path), its order and projection, and a page.
99
+ * The URL's `page_size` and `page_token` win over the query's.
100
+ *
101
+ * The page token is an offset here, opaque to the caller as the API's is.
102
+ */
103
+ export const applyListQuery = (
104
+ records: AnyRecord[],
105
+ query: ListQuery,
106
+ url: { pageSize?: string | null; pageToken?: string | null } = {},
107
+ ): { page: AnyRecord[]; nextPageToken: string | null } => {
108
+ const filtered = records.filter((record) =>
109
+ (query.filters ?? []).every((tuple) => matches(record, tuple)),
110
+ );
111
+ const sorted = ordered(filtered, query.order);
112
+
113
+ const pageSize = Number(url.pageSize ?? query.page_size ?? DEFAULT_PAGE_SIZE);
114
+ const offset = Number(url.pageToken ?? query.page_token ?? 0);
115
+ const end = offset + pageSize;
116
+
117
+ return {
118
+ page: project(sorted.slice(offset, end), query.projection),
119
+ nextPageToken: end < sorted.length ? String(end) : null,
120
+ };
121
+ };
122
+
123
+ /**
124
+ * A text search, the other form a list's `query` takes: an id, `#tag`, `&link_uid`, or a name
125
+ * prefix. The workflow and workspace lists take only this form.
126
+ */
127
+ export const applyTextSearch = (records: AnyRecord[], text: string): AnyRecord[] => {
128
+ if (text === "") return records;
129
+ if (text.startsWith("#")) {
130
+ return records.filter((r) => Array.isArray(r.tags) && r.tags.includes(text.slice(1)));
131
+ }
132
+ if (text.startsWith("&")) return records.filter((r) => r.link_uid === text.slice(1));
133
+ return records.filter((r) => r.id === text || String(r.name ?? "").startsWith(text));
134
+ };
@@ -0,0 +1,88 @@
1
+ import type { SetupServer } from "msw/node";
2
+
3
+ import type { ListQuery } from "./query";
4
+
5
+ /** A request the app sent, as a test asserts on it. */
6
+ export type RecordedRequest = {
7
+ method: string;
8
+ /** After `/api`: `/datastores`, `/datastores/dats_1`. */
9
+ path: string;
10
+ /** The URL's parameters other than `query`. */
11
+ params: Record<string, string>;
12
+ /** The `query` parameter, parsed when it is a JSON query; a text search stays a string. */
13
+ query?: ListQuery | string;
14
+ body?: unknown;
15
+ /** `0` for a network error a handler answered with `HttpResponse.error()`. */
16
+ status: number;
17
+ };
18
+
19
+ const API_PATH = /^.*?\/api(?=\/)/;
20
+
21
+ export const pathOf = (url: URL) => url.pathname.replace(API_PATH, "");
22
+
23
+ export const parsedQuery = (raw: string | null): ListQuery | string | undefined => {
24
+ if (raw === null) return undefined;
25
+ try {
26
+ const parsed: unknown = JSON.parse(raw);
27
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
28
+ ? (parsed as ListQuery)
29
+ : raw;
30
+ } catch {
31
+ return raw;
32
+ }
33
+ };
34
+
35
+ const parsedBody = (text: string): unknown => {
36
+ if (text === "") return undefined;
37
+ try {
38
+ return JSON.parse(text);
39
+ } catch {
40
+ return text;
41
+ }
42
+ };
43
+
44
+ /**
45
+ * Records every request to `/api` the server answers, from MSW's own events rather than from the
46
+ * fake's handlers: a handler a test adds with `server.use` answers in their place, and a request
47
+ * it answered went missing from the log, as if the page had never sent it.
48
+ *
49
+ * The body is read from a clone taken when the request starts, before a handler consumes it. The
50
+ * entry is logged when the response goes out, in the order the responses do.
51
+ *
52
+ * @param {SetupServer} server - The server whose requests to record.
53
+ * @param {() => RecordedRequest[]} log - The current log; a getter, as it is replaced per test.
54
+ */
55
+ export const recordRequests = (server: SetupServer, log: () => RecordedRequest[]) => {
56
+ const started = new Map<string, RecordedRequest>();
57
+
58
+ server.events.on("request:start", ({ request, requestId }) => {
59
+ const url = new URL(request.url);
60
+ if (!API_PATH.test(url.pathname)) return;
61
+
62
+ const entry: RecordedRequest = {
63
+ method: request.method.toUpperCase(),
64
+ path: pathOf(url),
65
+ params: Object.fromEntries([...url.searchParams].filter(([key]) => key !== "query")),
66
+ query: parsedQuery(url.searchParams.get("query")),
67
+ body: undefined,
68
+ status: 0,
69
+ };
70
+ started.set(requestId, entry);
71
+ if (["POST", "PUT", "PATCH"].includes(entry.method)) {
72
+ void request
73
+ .clone()
74
+ .text()
75
+ .then((text) => {
76
+ entry.body = parsedBody(text);
77
+ });
78
+ }
79
+ });
80
+
81
+ server.events.on("response:mocked", ({ response, requestId }) => {
82
+ const entry = started.get(requestId);
83
+ if (!entry) return;
84
+ started.delete(requestId);
85
+ entry.status = response.status;
86
+ log().push(entry);
87
+ });
88
+ };