@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.
- package/README.md +342 -12
- package/dist/EveryskApp.d.ts +34 -2
- package/dist/EveryskApp.d.ts.map +1 -1
- package/dist/EveryskApp.js +35 -2
- package/dist/EveryskApp.js.map +1 -1
- package/dist/agGrid/handleRowsScrollEnd.d.ts +53 -0
- package/dist/agGrid/handleRowsScrollEnd.d.ts.map +1 -0
- package/dist/agGrid/handleRowsScrollEnd.js +47 -0
- package/dist/agGrid/handleRowsScrollEnd.js.map +1 -0
- package/dist/agGrid/index.d.ts +2 -0
- package/dist/agGrid/index.d.ts.map +1 -1
- package/dist/agGrid/index.js +2 -1
- package/dist/api/datastore.d.ts +10 -8
- package/dist/api/datastore.d.ts.map +1 -1
- package/dist/api/datastore.js +24 -55
- package/dist/api/datastore.js.map +1 -1
- package/dist/api/entityQuery.d.ts +46 -41
- package/dist/api/entityQuery.d.ts.map +1 -1
- package/dist/api/entityQuery.js +55 -14
- package/dist/api/entityQuery.js.map +1 -1
- package/dist/api/file.d.ts +26 -9
- package/dist/api/file.d.ts.map +1 -1
- package/dist/api/file.js +45 -55
- package/dist/api/file.js.map +1 -1
- package/dist/api/portfolio.d.ts +11 -8
- package/dist/api/portfolio.d.ts.map +1 -1
- package/dist/api/portfolio.js +24 -55
- package/dist/api/portfolio.js.map +1 -1
- package/dist/api/profile.d.ts +3 -2
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +4 -9
- package/dist/api/profile.js.map +1 -1
- package/dist/api/request.d.ts +106 -0
- package/dist/api/request.d.ts.map +1 -0
- package/dist/api/request.js +132 -0
- package/dist/api/request.js.map +1 -0
- package/dist/api/websocketsConfig.d.ts +7 -2
- package/dist/api/websocketsConfig.d.ts.map +1 -1
- package/dist/api/websocketsConfig.js +2 -10
- package/dist/api/websocketsConfig.js.map +1 -1
- package/dist/api/workflow.d.ts +84 -89
- package/dist/api/workflow.d.ts.map +1 -1
- package/dist/api/workflow.js +121 -208
- package/dist/api/workflow.js.map +1 -1
- package/dist/api/workspace.d.ts +18 -8
- package/dist/api/workspace.d.ts.map +1 -1
- package/dist/api/workspace.js +21 -26
- package/dist/api/workspace.js.map +1 -1
- package/dist/components/ui/Footer/index.d.ts.map +1 -1
- package/dist/components/ui/Footer/index.js.map +1 -1
- package/dist/components/ui/GlobalLoading/index.d.ts.map +1 -1
- package/dist/components/ui/GlobalLoading/index.js.map +1 -1
- package/dist/components/ui/PageContainer/index.d.ts +1 -1
- package/dist/components/ui/PageContainer/index.d.ts.map +1 -1
- package/dist/components/ui/PageContainer/index.js.map +1 -1
- package/dist/components/ui/Toolbar/index.d.ts +40 -3
- package/dist/components/ui/Toolbar/index.d.ts.map +1 -1
- package/dist/components/ui/Toolbar/index.js +42 -15
- package/dist/components/ui/Toolbar/index.js.map +1 -1
- package/dist/contexts/appAlertContext/provider.js.map +1 -1
- package/dist/contexts/appAlertContext/types.d.ts.map +1 -1
- package/dist/contexts/appConfigContext/index.d.ts.map +1 -1
- package/dist/contexts/appConfigContext/index.js.map +1 -1
- package/dist/contexts/appConfigContext/provider.d.ts.map +1 -1
- package/dist/contexts/appConfigContext/provider.js.map +1 -1
- package/dist/contexts/webSocketContext/handler.d.ts +3 -2
- package/dist/contexts/webSocketContext/handler.d.ts.map +1 -1
- package/dist/contexts/webSocketContext/handler.js.map +1 -1
- package/dist/contexts/webSocketContext/hooks/useWebSocket/index.d.ts +1 -1
- package/dist/contexts/webSocketContext/hooks/useWebSocket/index.js +1 -1
- package/dist/contexts/webSocketContext/hooks/useWebSocket/index.js.map +1 -1
- package/dist/contexts/webSocketContext/provider.d.ts +6 -3
- package/dist/contexts/webSocketContext/provider.d.ts.map +1 -1
- package/dist/contexts/webSocketContext/provider.js +42 -7
- package/dist/contexts/webSocketContext/provider.js.map +1 -1
- package/dist/contexts/webSocketContext/types.d.ts +7 -5
- package/dist/contexts/webSocketContext/types.d.ts.map +1 -1
- package/dist/file/index.d.ts +2 -0
- package/dist/file/index.d.ts.map +1 -1
- package/dist/hooks/datastore/useDatastoreMutations.d.ts +4 -4
- package/dist/hooks/datastore/useDatastoreMutations.d.ts.map +1 -1
- package/dist/hooks/datastore/useDatastoreMutations.js +17 -55
- package/dist/hooks/datastore/useDatastoreMutations.js.map +1 -1
- package/dist/hooks/datastore/useFetchDatastore.d.ts +30 -41
- package/dist/hooks/datastore/useFetchDatastore.d.ts.map +1 -1
- package/dist/hooks/datastore/useFetchDatastore.js +25 -59
- package/dist/hooks/datastore/useFetchDatastore.js.map +1 -1
- package/dist/hooks/datastore/useFetchDatastores.d.ts +47 -52
- package/dist/hooks/datastore/useFetchDatastores.d.ts.map +1 -1
- package/dist/hooks/datastore/useFetchDatastores.js +55 -70
- package/dist/hooks/datastore/useFetchDatastores.js.map +1 -1
- package/dist/hooks/file/fileData.d.ts +17 -0
- package/dist/hooks/file/fileData.d.ts.map +1 -0
- package/dist/hooks/file/fileData.js +10 -0
- package/dist/hooks/file/fileData.js.map +1 -0
- package/dist/hooks/file/useFetchFile.d.ts +38 -58
- package/dist/hooks/file/useFetchFile.d.ts.map +1 -1
- package/dist/hooks/file/useFetchFile.js +26 -66
- package/dist/hooks/file/useFetchFile.js.map +1 -1
- package/dist/hooks/file/useFetchFiles.d.ts +18 -52
- package/dist/hooks/file/useFetchFiles.d.ts.map +1 -1
- package/dist/hooks/file/useFetchFiles.js +53 -69
- package/dist/hooks/file/useFetchFiles.js.map +1 -1
- package/dist/hooks/file/useFileMutations.d.ts +9 -8
- package/dist/hooks/file/useFileMutations.d.ts.map +1 -1
- package/dist/hooks/file/useFileMutations.js +23 -60
- package/dist/hooks/file/useFileMutations.js.map +1 -1
- package/dist/hooks/portfolio/useFetchPortfolio.d.ts +16 -50
- package/dist/hooks/portfolio/useFetchPortfolio.d.ts.map +1 -1
- package/dist/hooks/portfolio/useFetchPortfolio.js +15 -63
- package/dist/hooks/portfolio/useFetchPortfolio.js.map +1 -1
- package/dist/hooks/portfolio/useFetchPortfolios.d.ts +13 -52
- package/dist/hooks/portfolio/useFetchPortfolios.d.ts.map +1 -1
- package/dist/hooks/portfolio/useFetchPortfolios.js +29 -71
- package/dist/hooks/portfolio/useFetchPortfolios.js.map +1 -1
- package/dist/hooks/portfolio/usePortfolioMutations.d.ts +8 -8
- package/dist/hooks/portfolio/usePortfolioMutations.d.ts.map +1 -1
- package/dist/hooks/portfolio/usePortfolioMutations.js +20 -58
- package/dist/hooks/portfolio/usePortfolioMutations.js.map +1 -1
- package/dist/hooks/profile/useFetchProfile.d.ts +18 -44
- package/dist/hooks/profile/useFetchProfile.d.ts.map +1 -1
- package/dist/hooks/profile/useFetchProfile.js +16 -48
- package/dist/hooks/profile/useFetchProfile.js.map +1 -1
- package/dist/hooks/useAxios.js.map +1 -1
- package/dist/hooks/useEntityInfiniteQuery.d.ts +88 -0
- package/dist/hooks/useEntityInfiniteQuery.d.ts.map +1 -0
- package/dist/hooks/useEntityInfiniteQuery.js +111 -0
- package/dist/hooks/useEntityInfiniteQuery.js.map +1 -0
- package/dist/hooks/useEntityMutation.d.ts +47 -0
- package/dist/hooks/useEntityMutation.d.ts.map +1 -0
- package/dist/hooks/useEntityMutation.js +58 -0
- package/dist/hooks/useEntityMutation.js.map +1 -0
- package/dist/hooks/useEntityQuery.d.ts +68 -0
- package/dist/hooks/useEntityQuery.d.ts.map +1 -0
- package/dist/hooks/useEntityQuery.js +75 -0
- package/dist/hooks/useEntityQuery.js.map +1 -0
- package/dist/hooks/workflow/useFetchWorkerExecution.d.ts +17 -34
- package/dist/hooks/workflow/useFetchWorkerExecution.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkerExecution.js +16 -46
- package/dist/hooks/workflow/useFetchWorkerExecution.js.map +1 -1
- package/dist/hooks/workflow/useFetchWorkerExecutions.d.ts +179 -41
- package/dist/hooks/workflow/useFetchWorkerExecutions.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkerExecutions.js +29 -62
- package/dist/hooks/workflow/useFetchWorkerExecutions.js.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflow.d.ts +16 -35
- package/dist/hooks/workflow/useFetchWorkflow.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflow.js +15 -48
- package/dist/hooks/workflow/useFetchWorkflow.js.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflowExecution.d.ts +17 -34
- package/dist/hooks/workflow/useFetchWorkflowExecution.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflowExecution.js +16 -46
- package/dist/hooks/workflow/useFetchWorkflowExecution.js.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflowExecutions.d.ts +22 -36
- package/dist/hooks/workflow/useFetchWorkflowExecutions.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflowExecutions.js +43 -56
- package/dist/hooks/workflow/useFetchWorkflowExecutions.js.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflows.d.ts +20 -37
- package/dist/hooks/workflow/useFetchWorkflows.d.ts.map +1 -1
- package/dist/hooks/workflow/useFetchWorkflows.js +32 -47
- package/dist/hooks/workflow/useFetchWorkflows.js.map +1 -1
- package/dist/hooks/workflow/useRunWorkflow.d.ts +21 -16
- package/dist/hooks/workflow/useRunWorkflow.d.ts.map +1 -1
- package/dist/hooks/workflow/useRunWorkflow.js +66 -29
- package/dist/hooks/workflow/useRunWorkflow.js.map +1 -1
- package/dist/hooks/workflow/useTriggerWorkflow.d.ts +20 -0
- package/dist/hooks/workflow/useTriggerWorkflow.d.ts.map +1 -0
- package/dist/hooks/workflow/useTriggerWorkflow.js +32 -0
- package/dist/hooks/workflow/useTriggerWorkflow.js.map +1 -0
- package/dist/hooks/workspace/useFetchWorkspace.d.ts +19 -34
- package/dist/hooks/workspace/useFetchWorkspace.d.ts.map +1 -1
- package/dist/hooks/workspace/useFetchWorkspace.js +16 -46
- package/dist/hooks/workspace/useFetchWorkspace.js.map +1 -1
- package/dist/hooks/workspace/useFetchWorkspaces.d.ts +17 -37
- package/dist/hooks/workspace/useFetchWorkspaces.d.ts.map +1 -1
- package/dist/hooks/workspace/useFetchWorkspaces.js +25 -47
- package/dist/hooks/workspace/useFetchWorkspaces.js.map +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/lib/abort.d.ts +58 -0
- package/dist/lib/abort.d.ts.map +1 -0
- package/dist/lib/abort.js +112 -0
- package/dist/lib/abort.js.map +1 -0
- package/dist/lib/datastoreTable.d.ts +59 -54
- package/dist/lib/datastoreTable.d.ts.map +1 -1
- package/dist/lib/datastoreTable.js +58 -62
- package/dist/lib/datastoreTable.js.map +1 -1
- package/dist/lib/file.d.ts +34 -0
- package/dist/lib/file.d.ts.map +1 -0
- package/dist/lib/file.js +63 -0
- package/dist/lib/file.js.map +1 -0
- package/dist/lib/queryClient.d.ts.map +1 -1
- package/dist/lib/queryClient.js.map +1 -1
- package/dist/lib/workflowRun.d.ts +91 -0
- package/dist/lib/workflowRun.d.ts.map +1 -0
- package/dist/lib/workflowRun.js +224 -0
- package/dist/lib/workflowRun.js.map +1 -0
- package/dist/socket/index.d.ts +1 -3
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +3 -5
- package/dist/test/mocks/index.d.ts +1 -1
- package/dist/test/mocks/index.d.ts.map +1 -1
- package/dist/test/msw/fakeApi.d.ts +43 -0
- package/dist/test/msw/fakeApi.d.ts.map +1 -0
- package/dist/test/msw/fakeApi.js +74 -0
- package/dist/test/msw/fakeApi.js.map +1 -0
- package/dist/test/msw/handlers.d.ts +20 -0
- package/dist/test/msw/handlers.d.ts.map +1 -0
- package/dist/test/msw/handlers.js +279 -0
- package/dist/test/msw/handlers.js.map +1 -0
- package/dist/test/msw/index.d.ts +5 -0
- package/dist/test/msw/index.d.ts.map +1 -0
- package/dist/test/msw/index.js +2 -0
- package/dist/test/msw/query.d.ts +34 -0
- package/dist/test/msw/query.d.ts.map +1 -0
- package/dist/test/msw/query.js +96 -0
- package/dist/test/msw/query.js.map +1 -0
- package/dist/test/msw/recorder.d.ts +30 -0
- package/dist/test/msw/recorder.d.ts.map +1 -0
- package/dist/test/msw/recorder.js +65 -0
- package/dist/test/msw/recorder.js.map +1 -0
- package/dist/test/msw/store.d.ts +57 -0
- package/dist/test/msw/store.d.ts.map +1 -0
- package/dist/test/msw/store.js +145 -0
- package/dist/test/msw/store.js.map +1 -0
- package/dist/test/renderWithProviders.d.ts +2 -2
- package/dist/test/renderWithProviders.d.ts.map +1 -1
- package/dist/test/renderWithProviders.js.map +1 -1
- package/dist/test/setup.js.map +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/overrides/index.d.ts +4 -2
- package/dist/theme/overrides/index.d.ts.map +1 -1
- package/dist/theme/overrides/index.js +5 -3
- package/dist/theme/overrides/index.js.map +1 -1
- package/dist/theme/overrides/muiButton.d.ts +2 -2
- package/dist/theme/overrides/muiButton.js.map +1 -1
- package/dist/theme/overrides/muiCssBaseline.d.ts.map +1 -1
- package/dist/theme/overrides/muiCssBaseline.js.map +1 -1
- package/dist/theme/overrides/muiDatePicker.js.map +1 -1
- package/dist/theme/overrides/muiFormControl.js.map +1 -1
- package/dist/theme/overrides/muiIconButton.d.ts +2 -2
- package/dist/theme/overrides/muiIconButton.js.map +1 -1
- package/dist/theme/overrides/muiSelect.d.ts.map +1 -1
- package/dist/theme/overrides/muiSelect.js.map +1 -1
- package/dist/theme/overrides/muiSvgIcon.d.ts.map +1 -1
- package/dist/theme/overrides/muiSvgIcon.js.map +1 -1
- package/dist/theme/overrides/muiTextField.js.map +1 -1
- package/dist/theme/overrides/muiToggleButton.d.ts +2 -2
- package/dist/theme/overrides/muiToggleButton.js.map +1 -1
- package/dist/theme/overrides/muiToggleButtonGroup.d.ts +2 -2
- package/dist/theme/overrides/muiToggleButtonGroup.js.map +1 -1
- package/dist/theme/themes/DefaultDark.js +2 -2
- package/dist/theme/themes/DefaultDark.js.map +1 -1
- package/dist/theme/themes/DefaultLight.js +2 -2
- package/dist/theme/themes/DefaultLight.js.map +1 -1
- package/dist/theme/themes/EveryskDark.js +2 -2
- package/dist/theme/themes/EveryskDark.js.map +1 -1
- package/dist/theme/themes/EveryskLight.js +2 -2
- package/dist/theme/themes/EveryskLight.js.map +1 -1
- package/dist/theme/themes/MidnightEmber.js +2 -2
- package/dist/theme/themes/MidnightEmber.js.map +1 -1
- package/dist/types/anyRecord.d.ts.map +1 -1
- package/dist/types/datastore.d.ts +35 -5
- package/dist/types/datastore.d.ts.map +1 -1
- package/dist/types/datastoreDataFormat.d.ts +13 -0
- package/dist/types/datastoreDataFormat.d.ts.map +1 -0
- package/dist/types/entityQuery.d.ts +86 -16
- package/dist/types/entityQuery.d.ts.map +1 -1
- package/dist/types/file.d.ts +7 -3
- package/dist/types/file.d.ts.map +1 -1
- package/dist/types/portfolio.d.ts +1 -3
- package/dist/types/portfolio.d.ts.map +1 -1
- package/dist/types/profile.d.ts +0 -10
- package/dist/types/profile.d.ts.map +1 -1
- package/dist/types/workflow.d.ts +35 -6
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workspace.d.ts +1 -2
- package/dist/types/workspace.d.ts.map +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/package.json +10 -1
- package/src/EveryskApp.tsx +53 -2
- package/src/agGrid/handleRowsScrollEnd.ts +71 -0
- package/src/agGrid/index.ts +5 -1
- package/src/api/datastore.ts +79 -107
- package/src/api/entityQuery.ts +86 -17
- package/src/api/file.ts +110 -103
- package/src/api/portfolio.ts +82 -125
- package/src/api/profile.ts +25 -18
- package/src/api/request.ts +168 -0
- package/src/api/websocketsConfig.ts +11 -19
- package/src/api/workflow.ts +232 -334
- package/src/api/workspace.ts +43 -43
- package/src/components/ui/Footer/index.tsx +8 -7
- package/src/components/ui/GlobalLoading/index.tsx +13 -3
- package/src/components/ui/PageContainer/index.tsx +9 -2
- package/src/components/ui/Toolbar/index.tsx +105 -30
- package/src/contexts/appAlertContext/provider.tsx +1 -1
- package/src/contexts/appAlertContext/types.ts +3 -3
- package/src/contexts/appConfigContext/index.tsx +1 -1
- package/src/contexts/appConfigContext/provider.tsx +1 -5
- package/src/contexts/webSocketContext/handler.ts +8 -2
- package/src/contexts/webSocketContext/hooks/useWebSocket/index.ts +1 -1
- package/src/contexts/webSocketContext/provider.tsx +64 -14
- package/src/contexts/webSocketContext/types.ts +7 -6
- package/src/file/index.ts +2 -0
- package/src/hooks/datastore/useDatastoreMutations.ts +19 -60
- package/src/hooks/datastore/useFetchDatastore.ts +45 -73
- package/src/hooks/datastore/useFetchDatastores.ts +106 -131
- package/src/hooks/file/fileData.ts +19 -0
- package/src/hooks/file/useFetchFile.ts +41 -76
- package/src/hooks/file/useFetchFiles.ts +92 -87
- package/src/hooks/file/useFileMutations.ts +23 -73
- package/src/hooks/portfolio/useFetchPortfolio.ts +26 -78
- package/src/hooks/portfolio/useFetchPortfolios.ts +50 -93
- package/src/hooks/portfolio/usePortfolioMutations.ts +22 -72
- package/src/hooks/profile/useFetchProfile.ts +24 -65
- package/src/hooks/useAxios.ts +5 -5
- package/src/hooks/useEntityInfiniteQuery.ts +193 -0
- package/src/hooks/useEntityMutation.ts +70 -0
- package/src/hooks/useEntityQuery.ts +117 -0
- package/src/hooks/workflow/useFetchWorkerExecution.ts +27 -56
- package/src/hooks/workflow/useFetchWorkerExecutions.ts +54 -79
- package/src/hooks/workflow/useFetchWorkflow.ts +24 -58
- package/src/hooks/workflow/useFetchWorkflowExecution.ts +27 -57
- package/src/hooks/workflow/useFetchWorkflowExecutions.ts +64 -73
- package/src/hooks/workflow/useFetchWorkflows.ts +48 -64
- package/src/hooks/workflow/useRunWorkflow.ts +82 -36
- package/src/hooks/workflow/useTriggerWorkflow.ts +36 -0
- package/src/hooks/workspace/useFetchWorkspace.ts +25 -58
- package/src/hooks/workspace/useFetchWorkspaces.ts +38 -69
- package/src/index.ts +34 -19
- package/src/lib/abort.ts +131 -0
- package/src/lib/datastoreTable.ts +127 -74
- package/src/lib/file.ts +67 -0
- package/src/lib/queryClient.ts +11 -13
- package/src/lib/workflowRun.ts +329 -0
- package/src/socket/index.ts +1 -3
- package/src/test/mocks/index.ts +1 -5
- package/src/test/msw/fakeApi.ts +83 -0
- package/src/test/msw/handlers.ts +350 -0
- package/src/test/msw/index.ts +9 -0
- package/src/test/msw/query.ts +134 -0
- package/src/test/msw/recorder.ts +88 -0
- package/src/test/msw/store.ts +182 -0
- package/src/test/renderWithProviders.tsx +8 -3
- package/src/test/setup.ts +16 -16
- package/src/theme/index.ts +1 -1
- package/src/theme/overrides/index.ts +5 -3
- package/src/theme/overrides/muiButton.ts +3 -3
- package/src/theme/overrides/muiCssBaseline.ts +11 -12
- package/src/theme/overrides/muiDatePicker.ts +2 -2
- package/src/theme/overrides/muiFormControl.ts +2 -2
- package/src/theme/overrides/muiIconButton.ts +3 -3
- package/src/theme/overrides/muiSelect.ts +2 -2
- package/src/theme/overrides/muiSvgIcon.ts +2 -2
- package/src/theme/overrides/muiTextField.ts +1 -1
- package/src/theme/overrides/muiToggleButton.ts +4 -4
- package/src/theme/overrides/muiToggleButtonGroup.ts +4 -4
- package/src/theme/themes/DefaultDark.ts +2 -2
- package/src/theme/themes/DefaultLight.ts +2 -2
- package/src/theme/themes/EveryskDark.ts +2 -2
- package/src/theme/themes/EveryskLight.ts +2 -2
- package/src/theme/themes/MidnightEmber.ts +15 -15
- package/src/types/anyRecord.ts +2 -2
- package/src/types/datastore.ts +56 -22
- package/src/types/datastoreDataFormat.ts +12 -0
- package/src/types/entityQuery.ts +122 -38
- package/src/types/file.ts +8 -6
- package/src/types/portfolio.ts +70 -74
- package/src/types/profile.ts +0 -11
- package/src/types/workflow.ts +38 -9
- package/src/types/workspace.ts +2 -4
- package/src/ui/index.ts +1 -1
- package/src/workflow/index.ts +5 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { QueryError, applyListQuery, applyTextSearch } from "./query.js";
|
|
2
|
+
import { parsedQuery, pathOf } from "./recorder.js";
|
|
3
|
+
import { complete, newId, nowSeconds } from "./store.js";
|
|
4
|
+
import { HttpResponse, http } from "msw";
|
|
5
|
+
//#region src/test/msw/handlers.ts
|
|
6
|
+
/** The page size a text-query list answers with when none is given. Source: `ConfigDefaults.py`. */
|
|
7
|
+
var TEXT_PAGE_SIZE = 10;
|
|
8
|
+
var errorBody = (status, message) => ({
|
|
9
|
+
code: status,
|
|
10
|
+
message
|
|
11
|
+
});
|
|
12
|
+
var notFound = (entity, id) => ({
|
|
13
|
+
status: 404,
|
|
14
|
+
json: errorBody(404, `${entity} ${id} not found`)
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a responder: answers with a failure the test queued for the request,
|
|
18
|
+
* with the API's error body when the responder throws a `QueryError`, or with what it returns.
|
|
19
|
+
*/
|
|
20
|
+
var answering = (state, respond) => async ({ request }) => {
|
|
21
|
+
const url = new URL(request.url);
|
|
22
|
+
const path = pathOf(url);
|
|
23
|
+
const method = request.method.toUpperCase();
|
|
24
|
+
const body = ["POST", "PUT"].includes(method) ? await request.json().catch(() => void 0) : void 0;
|
|
25
|
+
const failure = state.takeFailure(method, path);
|
|
26
|
+
let answer;
|
|
27
|
+
if (failure) answer = {
|
|
28
|
+
status: failure.status,
|
|
29
|
+
json: errorBody(failure.status, failure.message)
|
|
30
|
+
};
|
|
31
|
+
else try {
|
|
32
|
+
answer = respond({
|
|
33
|
+
url,
|
|
34
|
+
path,
|
|
35
|
+
segments: path.split("/").filter(Boolean),
|
|
36
|
+
body
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (!(error instanceof QueryError)) throw error;
|
|
40
|
+
answer = {
|
|
41
|
+
status: error.status,
|
|
42
|
+
json: errorBody(error.status, error.message)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return HttpResponse.json(answer.json, { status: answer.status ?? 200 });
|
|
46
|
+
};
|
|
47
|
+
/** A page of a text-query list: the URL's `page_size` (10 by default) and `page_token`. */
|
|
48
|
+
var textPage = (records, url) => {
|
|
49
|
+
const offset = Number(url.searchParams.get("page_token") ?? 0);
|
|
50
|
+
const end = offset + Number(url.searchParams.get("page_size") ?? TEXT_PAGE_SIZE);
|
|
51
|
+
return {
|
|
52
|
+
page: records.slice(offset, end),
|
|
53
|
+
nextPageToken: end < records.length ? String(end) : null
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The five routes of an entity listed by JSON query: list, read, create, update and delete,
|
|
58
|
+
* each answered from the store with the API's envelopes — the plural key with
|
|
59
|
+
* `next_page_token` for a list, the singular key for one record, and a delete by id answering
|
|
60
|
+
* one `{ id, name, deleted }`.
|
|
61
|
+
*/
|
|
62
|
+
var jsonQueryEntity = (state, entity) => {
|
|
63
|
+
const { plural, singular, label } = entity;
|
|
64
|
+
const records = () => state.store()[plural];
|
|
65
|
+
const replace = (next) => {
|
|
66
|
+
state.store()[plural] = next;
|
|
67
|
+
};
|
|
68
|
+
return [
|
|
69
|
+
http.get(`*/api/${plural}`, answering(state, ({ url }) => {
|
|
70
|
+
const query = parsedQuery(url.searchParams.get("query"));
|
|
71
|
+
if (typeof query === "object") {
|
|
72
|
+
const { page, nextPageToken } = applyListQuery(records(), query, {
|
|
73
|
+
pageSize: url.searchParams.get("page_size"),
|
|
74
|
+
pageToken: url.searchParams.get("page_token")
|
|
75
|
+
});
|
|
76
|
+
return { json: {
|
|
77
|
+
[plural]: page,
|
|
78
|
+
next_page_token: nextPageToken
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
const { page, nextPageToken } = textPage(applyTextSearch(records(), query ?? ""), url);
|
|
82
|
+
return { json: {
|
|
83
|
+
[plural]: page,
|
|
84
|
+
next_page_token: nextPageToken
|
|
85
|
+
} };
|
|
86
|
+
})),
|
|
87
|
+
http.get(`*/api/${plural}/:id`, answering(state, ({ url, segments }) => {
|
|
88
|
+
const id = segments[1] ?? "";
|
|
89
|
+
if (url.searchParams.get("workspace") === "") return {
|
|
90
|
+
status: 403,
|
|
91
|
+
json: errorBody(403, "An empty workspace is refused.")
|
|
92
|
+
};
|
|
93
|
+
const found = records().find((r) => r.id === id);
|
|
94
|
+
if (!found) return notFound(label, id);
|
|
95
|
+
return { json: { [singular]: entity.read ? entity.read(found, url) : found } };
|
|
96
|
+
})),
|
|
97
|
+
http.post(`*/api/${plural}`, answering(state, ({ body }) => {
|
|
98
|
+
const now = nowSeconds();
|
|
99
|
+
const created = entity.create({
|
|
100
|
+
...body,
|
|
101
|
+
id: void 0,
|
|
102
|
+
created: now,
|
|
103
|
+
updated: now
|
|
104
|
+
});
|
|
105
|
+
records().push(created);
|
|
106
|
+
return { json: { [singular]: created } };
|
|
107
|
+
})),
|
|
108
|
+
http.put(`*/api/${plural}/:id`, answering(state, ({ segments, body }) => {
|
|
109
|
+
const id = segments[1] ?? "";
|
|
110
|
+
const index = records().findIndex((r) => r.id === id);
|
|
111
|
+
if (index === -1) return notFound(label, id);
|
|
112
|
+
const updated = {
|
|
113
|
+
...records()[index],
|
|
114
|
+
...body,
|
|
115
|
+
id,
|
|
116
|
+
updated: nowSeconds()
|
|
117
|
+
};
|
|
118
|
+
records()[index] = updated;
|
|
119
|
+
return { json: { [singular]: updated } };
|
|
120
|
+
})),
|
|
121
|
+
http.delete(`*/api/${plural}/:id`, answering(state, ({ segments }) => {
|
|
122
|
+
const id = segments[1] ?? "";
|
|
123
|
+
const found = records().find((r) => r.id === id);
|
|
124
|
+
if (!found) return notFound(label, id);
|
|
125
|
+
replace(records().filter((r) => r.id !== id));
|
|
126
|
+
return { json: { [singular]: {
|
|
127
|
+
id,
|
|
128
|
+
name: found.name,
|
|
129
|
+
deleted: true
|
|
130
|
+
} } };
|
|
131
|
+
}))
|
|
132
|
+
];
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Worker executions as `GET /workflows/:id/worker_executions` groups them: one group per worker,
|
|
136
|
+
* in the order the workers first appear, a worker with several runs as a parallel group.
|
|
137
|
+
*/
|
|
138
|
+
var groupWorkerExecutions = (executions) => {
|
|
139
|
+
const byWorker = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const execution of executions) byWorker.set(execution.worker_id, [...byWorker.get(execution.worker_id) ?? [], execution]);
|
|
141
|
+
const groups = [...byWorker.values()].map((runs) => {
|
|
142
|
+
const base = {
|
|
143
|
+
worker_id: runs[0].worker_id,
|
|
144
|
+
worker_name: runs[0].worker_name
|
|
145
|
+
};
|
|
146
|
+
return runs.length > 1 ? {
|
|
147
|
+
...base,
|
|
148
|
+
is_parallel_execution: true,
|
|
149
|
+
executions: runs
|
|
150
|
+
} : {
|
|
151
|
+
...base,
|
|
152
|
+
is_parallel_execution: false,
|
|
153
|
+
execution: runs[0]
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
const parallel = [...byWorker.values()].filter((runs) => runs.length > 1);
|
|
157
|
+
return {
|
|
158
|
+
worker_executions: groups,
|
|
159
|
+
parallel_index_max: parallel.length > 0 ? Math.max(...parallel.map((r) => r.length - 1)) : -1
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
var workflowHandlers = (state) => [
|
|
163
|
+
http.get("*/api/workflows", answering(state, ({ url }) => {
|
|
164
|
+
const workspace = url.searchParams.get("workspace") ?? "main";
|
|
165
|
+
const inWorkspace = state.store().workflows.filter((w) => w.workspace === workspace);
|
|
166
|
+
const { page, nextPageToken } = textPage(applyTextSearch(inWorkspace, url.searchParams.get("query") ?? ""), url);
|
|
167
|
+
return { json: {
|
|
168
|
+
workflows: page,
|
|
169
|
+
next_page_token: nextPageToken
|
|
170
|
+
} };
|
|
171
|
+
})),
|
|
172
|
+
http.get("*/api/workflows/:id", answering(state, ({ segments }) => {
|
|
173
|
+
const id = segments[1] ?? "";
|
|
174
|
+
const found = state.store().workflows.find((w) => w.id === id);
|
|
175
|
+
return found ? { json: { workflow: found } } : notFound("Workflow", id);
|
|
176
|
+
})),
|
|
177
|
+
http.post("*/api/workflows/:id/run", answering(state, ({ segments, body }) => {
|
|
178
|
+
const id = segments[1] ?? "";
|
|
179
|
+
const workflow = state.store().workflows.find((w) => w.id === id);
|
|
180
|
+
if (!workflow) return notFound("Workflow", id);
|
|
181
|
+
const now = nowSeconds();
|
|
182
|
+
const execution = complete.workflowExecution({
|
|
183
|
+
id: newId("wfex"),
|
|
184
|
+
workflow_id: id,
|
|
185
|
+
workflow_name: workflow.name,
|
|
186
|
+
workspace: body?.workspace ?? workflow.workspace,
|
|
187
|
+
status: "RUNNING",
|
|
188
|
+
run_status: "RUNNING",
|
|
189
|
+
started: now,
|
|
190
|
+
created: now,
|
|
191
|
+
updated: now
|
|
192
|
+
});
|
|
193
|
+
state.store().workflowExecutions.push(execution);
|
|
194
|
+
return {
|
|
195
|
+
status: 202,
|
|
196
|
+
json: {
|
|
197
|
+
workflow_execution: execution,
|
|
198
|
+
result: null
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
})),
|
|
202
|
+
http.get("*/api/workflows/:id/workflow_executions", answering(state, ({ url, segments }) => {
|
|
203
|
+
const workflowId = segments[1] ?? "";
|
|
204
|
+
const executions = state.store().workflowExecutions.filter((e) => e.workflow_id === workflowId);
|
|
205
|
+
const executionId = url.searchParams.get("workflow_execution_id");
|
|
206
|
+
if (executionId) {
|
|
207
|
+
const found = executions.find((e) => e.id === executionId);
|
|
208
|
+
return found ? { json: { workflow_execution: found } } : notFound("Workflow execution", executionId);
|
|
209
|
+
}
|
|
210
|
+
const workerId = url.searchParams.get("worker_id");
|
|
211
|
+
const { page, nextPageToken } = textPage(workerId ? executions.filter((e) => state.store().workerExecutions.some((w) => w.workflow_execution_id === e.id && w.worker_id === workerId)) : executions, url);
|
|
212
|
+
return { json: {
|
|
213
|
+
workflow_executions: page,
|
|
214
|
+
next_page_token: nextPageToken
|
|
215
|
+
} };
|
|
216
|
+
})),
|
|
217
|
+
http.get("*/api/workflows/:id/worker_executions", answering(state, ({ url, segments }) => {
|
|
218
|
+
const workflowId = segments[1] ?? "";
|
|
219
|
+
const runs = state.store().workerExecutions.filter((w) => w.workflow_id === workflowId);
|
|
220
|
+
const workerExecutionId = url.searchParams.get("worker_execution_id");
|
|
221
|
+
if (workerExecutionId) {
|
|
222
|
+
const found = runs.find((w) => w.id === workerExecutionId);
|
|
223
|
+
return found ? { json: { worker_execution: found } } : notFound("Worker execution", workerExecutionId);
|
|
224
|
+
}
|
|
225
|
+
const executionId = url.searchParams.get("workflow_execution_id");
|
|
226
|
+
return { json: groupWorkerExecutions(runs.filter((w) => w.workflow_execution_id === executionId)) };
|
|
227
|
+
}))
|
|
228
|
+
];
|
|
229
|
+
var workspaceHandlers = (state) => [http.get("*/api/workspaces", answering(state, ({ url }) => {
|
|
230
|
+
const { page, nextPageToken } = textPage(applyTextSearch(state.store().workspaces, url.searchParams.get("query") ?? ""), url);
|
|
231
|
+
return { json: {
|
|
232
|
+
workspaces: page,
|
|
233
|
+
next_page_token: nextPageToken
|
|
234
|
+
} };
|
|
235
|
+
})), http.get("*/api/workspaces/:name", answering(state, ({ segments }) => {
|
|
236
|
+
const name = decodeURIComponent(segments[1] ?? "");
|
|
237
|
+
const found = state.store().workspaces.find((w) => w.name === name);
|
|
238
|
+
return found ? { json: { workspace: found } } : notFound("Workspace", name);
|
|
239
|
+
}))];
|
|
240
|
+
/** Every route the SDK's request functions call. */
|
|
241
|
+
var fakeApiHandlers = (state) => [
|
|
242
|
+
...jsonQueryEntity(state, {
|
|
243
|
+
plural: "datastores",
|
|
244
|
+
singular: "datastore",
|
|
245
|
+
label: "Datastore",
|
|
246
|
+
create: (r) => complete.datastore(r)
|
|
247
|
+
}),
|
|
248
|
+
...jsonQueryEntity(state, {
|
|
249
|
+
plural: "files",
|
|
250
|
+
singular: "file",
|
|
251
|
+
label: "File",
|
|
252
|
+
create: (r) => complete.file({
|
|
253
|
+
...r,
|
|
254
|
+
hash: void 0,
|
|
255
|
+
url: void 0
|
|
256
|
+
}),
|
|
257
|
+
read: (file, url) => url.searchParams.get("with_data") === "false" ? {
|
|
258
|
+
...file,
|
|
259
|
+
data: null
|
|
260
|
+
} : file
|
|
261
|
+
}),
|
|
262
|
+
...jsonQueryEntity(state, {
|
|
263
|
+
plural: "portfolios",
|
|
264
|
+
singular: "portfolio",
|
|
265
|
+
label: "Portfolio",
|
|
266
|
+
create: (r) => complete.portfolio(r)
|
|
267
|
+
}),
|
|
268
|
+
...workflowHandlers(state),
|
|
269
|
+
...workspaceHandlers(state),
|
|
270
|
+
http.get("*/api/profile", answering(state, () => ({ json: {
|
|
271
|
+
status: "OK",
|
|
272
|
+
profile: state.store().profile
|
|
273
|
+
} }))),
|
|
274
|
+
http.get("*/api/websockets_config", answering(state, () => ({ json: state.store().websocketsConfig })))
|
|
275
|
+
];
|
|
276
|
+
//#endregion
|
|
277
|
+
export { errorBody, fakeApiHandlers };
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","names":[],"sources":["../../../src/test/msw/handlers.ts"],"sourcesContent":["import { http, HttpResponse, type HttpHandler, type JsonBodyType } from \"msw\";\n\nimport type { AnyRecord } from \"@/types/anyRecord\";\nimport type { WorkerExecution } from \"@/types/workflow\";\n\nimport { applyListQuery, applyTextSearch, QueryError } from \"./query\";\nimport { parsedQuery, pathOf } from \"./recorder\";\nimport { complete, newId, nowSeconds, type FakeApiStore } from \"./store\";\n\nexport type Failure = { method: string; path: string; status: number; message: string };\n\n/** What the handlers read and write: the store, and the failures queued. */\nexport type FakeState = {\n store: () => FakeApiStore;\n takeFailure: (method: string, path: string) => Failure | undefined;\n};\n\ntype Answer = { status?: number; json: JsonBodyType };\n\ntype Context = { url: URL; path: string; segments: string[]; body: unknown };\n\n/** The page size a text-query list answers with when none is given. Source: `ConfigDefaults.py`. */\nconst TEXT_PAGE_SIZE = 10;\n\nexport const errorBody = (status: number, message: string) => ({ code: status, message });\n\nconst notFound = (entity: string, id: string): Answer => ({\n status: 404,\n json: errorBody(404, `${entity} ${id} not found`),\n});\n\n/**\n * Wraps a responder: answers with a failure the test queued for the request,\n * with the API's error body when the responder throws a `QueryError`, or with what it returns.\n */\nconst answering =\n (state: FakeState, respond: (context: Context) => Answer) =>\n async ({ request }: { request: Request }) => {\n const url = new URL(request.url);\n const path = pathOf(url);\n const method = request.method.toUpperCase();\n const body = [\"POST\", \"PUT\"].includes(method)\n ? await request.json().catch(() => undefined)\n : undefined;\n\n const failure = state.takeFailure(method, path);\n let answer: Answer;\n if (failure) {\n answer = { status: failure.status, json: errorBody(failure.status, failure.message) };\n } else {\n try {\n answer = respond({ url, path, segments: path.split(\"/\").filter(Boolean), body });\n } catch (error) {\n if (!(error instanceof QueryError)) throw error;\n answer = { status: error.status, json: errorBody(error.status, error.message) };\n }\n }\n return HttpResponse.json(answer.json, { status: answer.status ?? 200 });\n };\n\n/** A page of a text-query list: the URL's `page_size` (10 by default) and `page_token`. */\nconst textPage = (records: AnyRecord[], url: URL) => {\n const offset = Number(url.searchParams.get(\"page_token\") ?? 0);\n const end = offset + Number(url.searchParams.get(\"page_size\") ?? TEXT_PAGE_SIZE);\n return {\n page: records.slice(offset, end),\n nextPageToken: end < records.length ? String(end) : null,\n };\n};\n\ntype JsonQueryEntity = {\n /** The URL segment and the list's key: `datastores`. */\n plural: \"datastores\" | \"files\" | \"portfolios\";\n /** A single record's key: `datastore`. */\n singular: string;\n /** For error messages: `Datastore`. */\n label: string;\n create: (record: AnyRecord) => AnyRecord;\n /** What a read by id answers, given the URL: a file leaves its data out on `with_data=false`. */\n read?: (record: AnyRecord, url: URL) => AnyRecord;\n};\n\n/**\n * The five routes of an entity listed by JSON query: list, read, create, update and delete,\n * each answered from the store with the API's envelopes — the plural key with\n * `next_page_token` for a list, the singular key for one record, and a delete by id answering\n * one `{ id, name, deleted }`.\n */\nconst jsonQueryEntity = (state: FakeState, entity: JsonQueryEntity): HttpHandler[] => {\n const { plural, singular, label } = entity;\n const records = () => state.store()[plural] as unknown as AnyRecord[];\n const replace = (next: AnyRecord[]) => {\n (state.store() as unknown as Record<string, AnyRecord[]>)[plural] = next;\n };\n\n return [\n http.get(\n `*/api/${plural}`,\n answering(state, ({ url }) => {\n const query = parsedQuery(url.searchParams.get(\"query\"));\n if (typeof query === \"object\") {\n const { page, nextPageToken } = applyListQuery(records(), query, {\n pageSize: url.searchParams.get(\"page_size\"),\n pageToken: url.searchParams.get(\"page_token\"),\n });\n return { json: { [plural]: page, next_page_token: nextPageToken } };\n }\n const { page, nextPageToken } = textPage(\n applyTextSearch(records(), query ?? \"\"),\n url,\n );\n return { json: { [plural]: page, next_page_token: nextPageToken } };\n }),\n ),\n http.get(\n `*/api/${plural}/:id`,\n answering(state, ({ url, segments }) => {\n const id = segments[1] ?? \"\";\n if (url.searchParams.get(\"workspace\") === \"\") {\n return { status: 403, json: errorBody(403, \"An empty workspace is refused.\") };\n }\n const found = records().find((r) => r.id === id);\n if (!found) return notFound(label, id);\n return { json: { [singular]: entity.read ? entity.read(found, url) : found } };\n }),\n ),\n http.post(\n `*/api/${plural}`,\n answering(state, ({ body }) => {\n const now = nowSeconds();\n const created = entity.create({\n ...(body as AnyRecord),\n id: undefined,\n created: now,\n updated: now,\n });\n records().push(created);\n return { json: { [singular]: created } };\n }),\n ),\n http.put(\n `*/api/${plural}/:id`,\n answering(state, ({ segments, body }) => {\n const id = segments[1] ?? \"\";\n const index = records().findIndex((r) => r.id === id);\n if (index === -1) return notFound(label, id);\n const updated = {\n ...records()[index],\n ...(body as AnyRecord),\n id,\n updated: nowSeconds(),\n };\n records()[index] = updated;\n return { json: { [singular]: updated } };\n }),\n ),\n http.delete(\n `*/api/${plural}/:id`,\n answering(state, ({ segments }) => {\n const id = segments[1] ?? \"\";\n const found = records().find((r) => r.id === id);\n if (!found) return notFound(label, id);\n replace(records().filter((r) => r.id !== id));\n return { json: { [singular]: { id, name: found.name, deleted: true } } };\n }),\n ),\n ];\n};\n\n/**\n * Worker executions as `GET /workflows/:id/worker_executions` groups them: one group per worker,\n * in the order the workers first appear, a worker with several runs as a parallel group.\n */\nconst groupWorkerExecutions = (executions: WorkerExecution[]) => {\n const byWorker = new Map<string, WorkerExecution[]>();\n for (const execution of executions) {\n byWorker.set(execution.worker_id, [\n ...(byWorker.get(execution.worker_id) ?? []),\n execution,\n ]);\n }\n const groups = [...byWorker.values()].map((runs) => {\n const base = { worker_id: runs[0]!.worker_id, worker_name: runs[0]!.worker_name };\n return runs.length > 1\n ? { ...base, is_parallel_execution: true, executions: runs }\n : { ...base, is_parallel_execution: false, execution: runs[0] };\n });\n const parallel = [...byWorker.values()].filter((runs) => runs.length > 1);\n return {\n worker_executions: groups,\n parallel_index_max:\n parallel.length > 0 ? Math.max(...parallel.map((r) => r.length - 1)) : -1,\n };\n};\n\nconst workflowHandlers = (state: FakeState): HttpHandler[] => [\n // The workflow list takes only a text search, under the `workspace` it is given.\n http.get(\n \"*/api/workflows\",\n answering(state, ({ url }) => {\n const workspace = url.searchParams.get(\"workspace\") ?? \"main\";\n const inWorkspace = state.store().workflows.filter((w) => w.workspace === workspace);\n const { page, nextPageToken } = textPage(\n applyTextSearch(inWorkspace, url.searchParams.get(\"query\") ?? \"\"),\n url,\n );\n return { json: { workflows: page, next_page_token: nextPageToken } };\n }),\n ),\n http.get(\n \"*/api/workflows/:id\",\n answering(state, ({ segments }) => {\n const id = segments[1] ?? \"\";\n const found = state.store().workflows.find((w) => w.id === id);\n return found ? { json: { workflow: found } } : notFound(\"Workflow\", id);\n }),\n ),\n // 202 with the execution it started, still RUNNING, and a `result` that is always null.\n http.post(\n \"*/api/workflows/:id/run\",\n answering(state, ({ segments, body }) => {\n const id = segments[1] ?? \"\";\n const workflow = state.store().workflows.find((w) => w.id === id);\n if (!workflow) return notFound(\"Workflow\", id);\n const now = nowSeconds();\n const execution = complete.workflowExecution({\n id: newId(\"wfex\"),\n workflow_id: id,\n workflow_name: workflow.name,\n workspace: (body as AnyRecord | undefined)?.workspace ?? workflow.workspace,\n status: \"RUNNING\",\n run_status: \"RUNNING\",\n started: now,\n created: now,\n updated: now,\n });\n state.store().workflowExecutions.push(execution);\n return { status: 202, json: { workflow_execution: execution, result: null } };\n }),\n ),\n // Ignores a JSON query: reads `workflow_execution_id`, `worker_id`, `page_size` and\n // `page_token` from the URL. With `workflow_execution_id` it answers that one execution.\n http.get(\n \"*/api/workflows/:id/workflow_executions\",\n answering(state, ({ url, segments }) => {\n const workflowId = segments[1] ?? \"\";\n const executions = state\n .store()\n .workflowExecutions.filter((e) => e.workflow_id === workflowId);\n const executionId = url.searchParams.get(\"workflow_execution_id\");\n if (executionId) {\n const found = executions.find((e) => e.id === executionId);\n return found\n ? { json: { workflow_execution: found } }\n : notFound(\"Workflow execution\", executionId);\n }\n const workerId = url.searchParams.get(\"worker_id\");\n const ran = workerId\n ? executions.filter((e) =>\n state\n .store()\n .workerExecutions.some(\n (w) => w.workflow_execution_id === e.id && w.worker_id === workerId,\n ),\n )\n : executions;\n const { page, nextPageToken } = textPage(ran, url);\n return { json: { workflow_executions: page, next_page_token: nextPageToken } };\n }),\n ),\n // Not paginated. With `worker_execution_id` it answers that one run; with\n // `workflow_execution_id`, every worker of that execution, grouped.\n http.get(\n \"*/api/workflows/:id/worker_executions\",\n answering(state, ({ url, segments }) => {\n const workflowId = segments[1] ?? \"\";\n const runs = state.store().workerExecutions.filter((w) => w.workflow_id === workflowId);\n const workerExecutionId = url.searchParams.get(\"worker_execution_id\");\n if (workerExecutionId) {\n const found = runs.find((w) => w.id === workerExecutionId);\n return found\n ? { json: { worker_execution: found } }\n : notFound(\"Worker execution\", workerExecutionId);\n }\n const executionId = url.searchParams.get(\"workflow_execution_id\");\n return {\n json: groupWorkerExecutions(\n runs.filter((w) => w.workflow_execution_id === executionId),\n ),\n };\n }),\n ),\n];\n\nconst workspaceHandlers = (state: FakeState): HttpHandler[] => [\n http.get(\n \"*/api/workspaces\",\n answering(state, ({ url }) => {\n const { page, nextPageToken } = textPage(\n applyTextSearch(state.store().workspaces, url.searchParams.get(\"query\") ?? \"\"),\n url,\n );\n return { json: { workspaces: page, next_page_token: nextPageToken } };\n }),\n ),\n // A workspace is identified by its name.\n http.get(\n \"*/api/workspaces/:name\",\n answering(state, ({ segments }) => {\n const name = decodeURIComponent(segments[1] ?? \"\");\n const found = state.store().workspaces.find((w) => w.name === name);\n return found ? { json: { workspace: found } } : notFound(\"Workspace\", name);\n }),\n ),\n];\n\n/** Every route the SDK's request functions call. */\nexport const fakeApiHandlers = (state: FakeState): HttpHandler[] => [\n ...jsonQueryEntity(state, {\n plural: \"datastores\",\n singular: \"datastore\",\n label: \"Datastore\",\n create: (r) => complete.datastore(r),\n }),\n ...jsonQueryEntity(state, {\n plural: \"files\",\n singular: \"file\",\n label: \"File\",\n create: (r) => complete.file({ ...r, hash: undefined, url: undefined }),\n read: (file, url) =>\n url.searchParams.get(\"with_data\") === \"false\" ? { ...file, data: null } : file,\n }),\n ...jsonQueryEntity(state, {\n plural: \"portfolios\",\n singular: \"portfolio\",\n label: \"Portfolio\",\n create: (r) => complete.portfolio(r),\n }),\n ...workflowHandlers(state),\n ...workspaceHandlers(state),\n // An app-only route; the SDK reads only `profile` from it.\n http.get(\n \"*/api/profile\",\n answering(state, () => ({ json: { status: \"OK\", profile: state.store().profile } })),\n ),\n http.get(\n \"*/api/websockets_config\",\n answering(state, () => ({ json: state.store().websocketsConfig })),\n ),\n];\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/test/msw/index.ts"],"names":[],"mappings":"AAKA,cAAc,WAAW,CAAC;AAC1B,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACzD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AnyRecord } from "../../types/anyRecord.d.ts";
|
|
2
|
+
/** The JSON query a list read sends in `?query=`, as `buildQueryObject` writes it. */
|
|
3
|
+
export type ListQuery = {
|
|
4
|
+
filters?: unknown[][];
|
|
5
|
+
order?: string[];
|
|
6
|
+
projection?: string | string[];
|
|
7
|
+
page_size?: number;
|
|
8
|
+
page_token?: string;
|
|
9
|
+
};
|
|
10
|
+
/** A query the API refuses: the handler answers it with the API's error body and status. */
|
|
11
|
+
export declare class QueryError extends Error {
|
|
12
|
+
status: number;
|
|
13
|
+
constructor(message: string, status?: number);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* applyListQuery — a JSON-query list read, as the API answers it: the query's filters (the
|
|
17
|
+
* `workspace` URL parameter is ignored on this path), its order and projection, and a page.
|
|
18
|
+
* The URL's `page_size` and `page_token` win over the query's.
|
|
19
|
+
*
|
|
20
|
+
* The page token is an offset here, opaque to the caller as the API's is.
|
|
21
|
+
*/
|
|
22
|
+
export declare const applyListQuery: (records: AnyRecord[], query: ListQuery, url?: {
|
|
23
|
+
pageSize?: string | null;
|
|
24
|
+
pageToken?: string | null;
|
|
25
|
+
}) => {
|
|
26
|
+
page: AnyRecord[];
|
|
27
|
+
nextPageToken: string | null;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A text search, the other form a list's `query` takes: an id, `#tag`, `&link_uid`, or a name
|
|
31
|
+
* prefix. The workflow and workspace lists take only this form.
|
|
32
|
+
*/
|
|
33
|
+
export declare const applyTextSearch: (records: AnyRecord[], text: string) => AnyRecord[];
|
|
34
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/test/msw/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG;IACpB,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAuCF,4FAA4F;AAC5F,qBAAa,UAAW,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;gBAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAM;CAK5C;AAqCD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GACvB,SAAS,SAAS,EAAE,EACpB,OAAO,SAAS,EAChB,MAAK;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,KAClE;IAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAcnD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,SAAS,EAAE,EAAE,MAAM,MAAM,KAAG,SAAS,EAO7E,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/test/msw/query.ts
|
|
2
|
+
/** The page size a JSON-query list answers with when none is given. Source: `ConfigDefaults.py`. */
|
|
3
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
4
|
+
var compare = (a, b) => {
|
|
5
|
+
if (a === b) return 0;
|
|
6
|
+
if (a === void 0 || a === null) return -1;
|
|
7
|
+
if (b === void 0 || b === null) return 1;
|
|
8
|
+
return a < b ? -1 : 1;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Each operator the SDK can send, as the API applies it. Keyed by `FilterOperator`, so an
|
|
12
|
+
* operator added to the SDK does not compile until the fake knows it, rather than failing inside
|
|
13
|
+
* an app's test.
|
|
14
|
+
*/
|
|
15
|
+
var OPERATORS = {
|
|
16
|
+
"=": (actual, value) => actual === value,
|
|
17
|
+
"!=": (actual, value) => actual !== value,
|
|
18
|
+
">": (actual, value) => compare(actual, value) > 0,
|
|
19
|
+
">=": (actual, value) => compare(actual, value) >= 0,
|
|
20
|
+
"<": (actual, value) => compare(actual, value) < 0,
|
|
21
|
+
"<=": (actual, value) => compare(actual, value) <= 0,
|
|
22
|
+
IN: (actual, value) => Array.isArray(value) && value.includes(actual),
|
|
23
|
+
NOT_IN: (actual, value) => Array.isArray(value) && !value.includes(actual)
|
|
24
|
+
};
|
|
25
|
+
var isOperator = (op) => typeof op === "string" && Object.hasOwn(OPERATORS, op);
|
|
26
|
+
/** A filter tuple as the API reads it: two items are `=`, three name the operator. */
|
|
27
|
+
var matches = (record, tuple) => {
|
|
28
|
+
const [field, op, value] = tuple.length === 2 ? [
|
|
29
|
+
tuple[0],
|
|
30
|
+
"=",
|
|
31
|
+
tuple[1]
|
|
32
|
+
] : tuple;
|
|
33
|
+
if (!isOperator(op)) throw new QueryError(`Unknown filter operator: ${String(op)}`);
|
|
34
|
+
return OPERATORS[op](record[field], value);
|
|
35
|
+
};
|
|
36
|
+
/** A query the API refuses: the handler answers it with the API's error body and status. */
|
|
37
|
+
var QueryError = class extends Error {
|
|
38
|
+
status;
|
|
39
|
+
constructor(message, status = 400) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "QueryError";
|
|
42
|
+
this.status = status;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var ordered = (records, order = []) => [...records].sort((a, b) => {
|
|
46
|
+
for (const key of order) {
|
|
47
|
+
const desc = key.startsWith("-");
|
|
48
|
+
const field = desc ? key.slice(1) : key;
|
|
49
|
+
const result = compare(a[field], b[field]);
|
|
50
|
+
if (result !== 0) return desc ? -result : result;
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* One field or an array, never a comma-joined string, and never inclusions mixed with
|
|
56
|
+
* exclusions: the API answers both with a 500 (`_normalize_projection`).
|
|
57
|
+
*/
|
|
58
|
+
var project = (records, projection) => {
|
|
59
|
+
if (projection === void 0 || projection === "") return records;
|
|
60
|
+
const fields = Array.isArray(projection) ? projection : [projection];
|
|
61
|
+
if (fields.some((f) => f.includes(","))) throw new QueryError("A projection is one field or an array, not a list in a string.", 500);
|
|
62
|
+
const excluded = fields.filter((f) => f.startsWith("-")).map((f) => f.slice(1));
|
|
63
|
+
if (excluded.length > 0 && excluded.length < fields.length) throw new QueryError("A projection cannot mix inclusions with exclusions.", 500);
|
|
64
|
+
return records.map((record) => excluded.length > 0 ? Object.fromEntries(Object.entries(record).filter(([k]) => !excluded.includes(k))) : Object.fromEntries(Object.entries(record).filter(([k]) => k === "id" || fields.includes(k))));
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* applyListQuery — a JSON-query list read, as the API answers it: the query's filters (the
|
|
68
|
+
* `workspace` URL parameter is ignored on this path), its order and projection, and a page.
|
|
69
|
+
* The URL's `page_size` and `page_token` win over the query's.
|
|
70
|
+
*
|
|
71
|
+
* The page token is an offset here, opaque to the caller as the API's is.
|
|
72
|
+
*/
|
|
73
|
+
var applyListQuery = (records, query, url = {}) => {
|
|
74
|
+
const sorted = ordered(records.filter((record) => (query.filters ?? []).every((tuple) => matches(record, tuple))), query.order);
|
|
75
|
+
const pageSize = Number(url.pageSize ?? query.page_size ?? DEFAULT_PAGE_SIZE);
|
|
76
|
+
const offset = Number(url.pageToken ?? query.page_token ?? 0);
|
|
77
|
+
const end = offset + pageSize;
|
|
78
|
+
return {
|
|
79
|
+
page: project(sorted.slice(offset, end), query.projection),
|
|
80
|
+
nextPageToken: end < sorted.length ? String(end) : null
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* A text search, the other form a list's `query` takes: an id, `#tag`, `&link_uid`, or a name
|
|
85
|
+
* prefix. The workflow and workspace lists take only this form.
|
|
86
|
+
*/
|
|
87
|
+
var applyTextSearch = (records, text) => {
|
|
88
|
+
if (text === "") return records;
|
|
89
|
+
if (text.startsWith("#")) return records.filter((r) => Array.isArray(r.tags) && r.tags.includes(text.slice(1)));
|
|
90
|
+
if (text.startsWith("&")) return records.filter((r) => r.link_uid === text.slice(1));
|
|
91
|
+
return records.filter((r) => r.id === text || String(r.name ?? "").startsWith(text));
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
export { QueryError, applyListQuery, applyTextSearch };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../../../src/test/msw/query.ts"],"sourcesContent":["import type { AnyRecord } from \"@/types/anyRecord\";\nimport type { FilterOperator } from \"@/types/entityQuery\";\n\n/** The JSON query a list read sends in `?query=`, as `buildQueryObject` writes it. */\nexport type ListQuery = {\n filters?: unknown[][];\n order?: string[];\n projection?: string | string[];\n page_size?: number;\n page_token?: string;\n};\n\n/** The page size a JSON-query list answers with when none is given. Source: `ConfigDefaults.py`. */\nconst DEFAULT_PAGE_SIZE = 20;\n\nconst compare = (a: unknown, b: unknown): number => {\n if (a === b) return 0;\n if (a === undefined || a === null) return -1;\n if (b === undefined || b === null) return 1;\n return (a as string | number) < (b as string | number) ? -1 : 1;\n};\n\n/**\n * Each operator the SDK can send, as the API applies it. Keyed by `FilterOperator`, so an\n * operator added to the SDK does not compile until the fake knows it, rather than failing inside\n * an app's test.\n */\nconst OPERATORS: Record<FilterOperator, (actual: unknown, value: unknown) => boolean> = {\n \"=\": (actual, value) => actual === value,\n \"!=\": (actual, value) => actual !== value,\n \">\": (actual, value) => compare(actual, value) > 0,\n \">=\": (actual, value) => compare(actual, value) >= 0,\n \"<\": (actual, value) => compare(actual, value) < 0,\n \"<=\": (actual, value) => compare(actual, value) <= 0,\n IN: (actual, value) => Array.isArray(value) && value.includes(actual),\n NOT_IN: (actual, value) => Array.isArray(value) && !value.includes(actual),\n};\n\nconst isOperator = (op: unknown): op is FilterOperator =>\n typeof op === \"string\" && Object.hasOwn(OPERATORS, op);\n\n/** A filter tuple as the API reads it: two items are `=`, three name the operator. */\nconst matches = (record: AnyRecord, tuple: unknown[]): boolean => {\n const [field, op, value] = tuple.length === 2 ? [tuple[0], \"=\", tuple[1]] : tuple;\n if (!isOperator(op)) throw new QueryError(`Unknown filter operator: ${String(op)}`);\n\n return OPERATORS[op](record[field as string], value);\n};\n\n/** A query the API refuses: the handler answers it with the API's error body and status. */\nexport class QueryError extends Error {\n status: number;\n\n constructor(message: string, status = 400) {\n super(message);\n this.name = \"QueryError\";\n this.status = status;\n }\n}\n\nconst ordered = (records: AnyRecord[], order: string[] = []): AnyRecord[] =>\n [...records].sort((a, b) => {\n for (const key of order) {\n const desc = key.startsWith(\"-\");\n const field = desc ? key.slice(1) : key;\n const result = compare(a[field], b[field]);\n if (result !== 0) return desc ? -result : result;\n }\n return 0;\n });\n\n/**\n * One field or an array, never a comma-joined string, and never inclusions mixed with\n * exclusions: the API answers both with a 500 (`_normalize_projection`).\n */\nconst project = (records: AnyRecord[], projection?: string | string[]): AnyRecord[] => {\n if (projection === undefined || projection === \"\") return records;\n const fields = Array.isArray(projection) ? projection : [projection];\n if (fields.some((f) => f.includes(\",\"))) {\n throw new QueryError(\"A projection is one field or an array, not a list in a string.\", 500);\n }\n const excluded = fields.filter((f) => f.startsWith(\"-\")).map((f) => f.slice(1));\n if (excluded.length > 0 && excluded.length < fields.length) {\n throw new QueryError(\"A projection cannot mix inclusions with exclusions.\", 500);\n }\n\n return records.map((record) =>\n excluded.length > 0\n ? Object.fromEntries(Object.entries(record).filter(([k]) => !excluded.includes(k)))\n : Object.fromEntries(\n Object.entries(record).filter(([k]) => k === \"id\" || fields.includes(k)),\n ),\n );\n};\n\n/**\n * applyListQuery — a JSON-query list read, as the API answers it: the query's filters (the\n * `workspace` URL parameter is ignored on this path), its order and projection, and a page.\n * The URL's `page_size` and `page_token` win over the query's.\n *\n * The page token is an offset here, opaque to the caller as the API's is.\n */\nexport const applyListQuery = (\n records: AnyRecord[],\n query: ListQuery,\n url: { pageSize?: string | null; pageToken?: string | null } = {},\n): { page: AnyRecord[]; nextPageToken: string | null } => {\n const filtered = records.filter((record) =>\n (query.filters ?? []).every((tuple) => matches(record, tuple)),\n );\n const sorted = ordered(filtered, query.order);\n\n const pageSize = Number(url.pageSize ?? query.page_size ?? DEFAULT_PAGE_SIZE);\n const offset = Number(url.pageToken ?? query.page_token ?? 0);\n const end = offset + pageSize;\n\n return {\n page: project(sorted.slice(offset, end), query.projection),\n nextPageToken: end < sorted.length ? String(end) : null,\n };\n};\n\n/**\n * A text search, the other form a list's `query` takes: an id, `#tag`, `&link_uid`, or a name\n * prefix. The workflow and workspace lists take only this form.\n */\nexport const applyTextSearch = (records: AnyRecord[], text: string): AnyRecord[] => {\n if (text === \"\") return records;\n if (text.startsWith(\"#\")) {\n return records.filter((r) => Array.isArray(r.tags) && r.tags.includes(text.slice(1)));\n }\n if (text.startsWith(\"&\")) return records.filter((r) => r.link_uid === text.slice(1));\n return records.filter((r) => r.id === text || String(r.name ?? \"\").startsWith(text));\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SetupServer } from "msw/node";
|
|
2
|
+
import type { ListQuery } from "./query";
|
|
3
|
+
/** A request the app sent, as a test asserts on it. */
|
|
4
|
+
export type RecordedRequest = {
|
|
5
|
+
method: string;
|
|
6
|
+
/** After `/api`: `/datastores`, `/datastores/dats_1`. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** The URL's parameters other than `query`. */
|
|
9
|
+
params: Record<string, string>;
|
|
10
|
+
/** The `query` parameter, parsed when it is a JSON query; a text search stays a string. */
|
|
11
|
+
query?: ListQuery | string;
|
|
12
|
+
body?: unknown;
|
|
13
|
+
/** `0` for a network error a handler answered with `HttpResponse.error()`. */
|
|
14
|
+
status: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const pathOf: (url: URL) => string;
|
|
17
|
+
export declare const parsedQuery: (raw: string | null) => ListQuery | string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Records every request to `/api` the server answers, from MSW's own events rather than from the
|
|
20
|
+
* fake's handlers: a handler a test adds with `server.use` answers in their place, and a request
|
|
21
|
+
* it answered went missing from the log, as if the page had never sent it.
|
|
22
|
+
*
|
|
23
|
+
* The body is read from a clone taken when the request starts, before a handler consumes it. The
|
|
24
|
+
* entry is logged when the response goes out, in the order the responses do.
|
|
25
|
+
*
|
|
26
|
+
* @param {SetupServer} server - The server whose requests to record.
|
|
27
|
+
* @param {() => RecordedRequest[]} log - The current log; a getter, as it is replaced per test.
|
|
28
|
+
*/
|
|
29
|
+
export declare const recordRequests: (server: SetupServer, log: () => RecordedRequest[]) => void;
|
|
30
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../src/test/msw/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,uDAAuD;AACvD,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,2FAA2F;IAC3F,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,eAAO,MAAM,MAAM,GAAI,KAAK,GAAG,WAAuC,CAAC;AAEvE,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,GAAG,IAAI,KAAG,SAAS,GAAG,MAAM,GAAG,SAUrE,CAAC;AAWF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,WAAW,EAAE,KAAK,MAAM,eAAe,EAAE,SAiC/E,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/test/msw/recorder.ts
|
|
2
|
+
var API_PATH = /^.*?\/api(?=\/)/;
|
|
3
|
+
var pathOf = (url) => url.pathname.replace(API_PATH, "");
|
|
4
|
+
var parsedQuery = (raw) => {
|
|
5
|
+
if (raw === null) return void 0;
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : raw;
|
|
9
|
+
} catch {
|
|
10
|
+
return raw;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var parsedBody = (text) => {
|
|
14
|
+
if (text === "") return void 0;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
} catch {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Records every request to `/api` the server answers, from MSW's own events rather than from the
|
|
23
|
+
* fake's handlers: a handler a test adds with `server.use` answers in their place, and a request
|
|
24
|
+
* it answered went missing from the log, as if the page had never sent it.
|
|
25
|
+
*
|
|
26
|
+
* The body is read from a clone taken when the request starts, before a handler consumes it. The
|
|
27
|
+
* entry is logged when the response goes out, in the order the responses do.
|
|
28
|
+
*
|
|
29
|
+
* @param {SetupServer} server - The server whose requests to record.
|
|
30
|
+
* @param {() => RecordedRequest[]} log - The current log; a getter, as it is replaced per test.
|
|
31
|
+
*/
|
|
32
|
+
var recordRequests = (server, log) => {
|
|
33
|
+
const started = /* @__PURE__ */ new Map();
|
|
34
|
+
server.events.on("request:start", ({ request, requestId }) => {
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
if (!API_PATH.test(url.pathname)) return;
|
|
37
|
+
const entry = {
|
|
38
|
+
method: request.method.toUpperCase(),
|
|
39
|
+
path: pathOf(url),
|
|
40
|
+
params: Object.fromEntries([...url.searchParams].filter(([key]) => key !== "query")),
|
|
41
|
+
query: parsedQuery(url.searchParams.get("query")),
|
|
42
|
+
body: void 0,
|
|
43
|
+
status: 0
|
|
44
|
+
};
|
|
45
|
+
started.set(requestId, entry);
|
|
46
|
+
if ([
|
|
47
|
+
"POST",
|
|
48
|
+
"PUT",
|
|
49
|
+
"PATCH"
|
|
50
|
+
].includes(entry.method)) request.clone().text().then((text) => {
|
|
51
|
+
entry.body = parsedBody(text);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
server.events.on("response:mocked", ({ response, requestId }) => {
|
|
55
|
+
const entry = started.get(requestId);
|
|
56
|
+
if (!entry) return;
|
|
57
|
+
started.delete(requestId);
|
|
58
|
+
entry.status = response.status;
|
|
59
|
+
log().push(entry);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { parsedQuery, pathOf, recordRequests };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","names":[],"sources":["../../../src/test/msw/recorder.ts"],"sourcesContent":["import type { SetupServer } from \"msw/node\";\n\nimport type { ListQuery } from \"./query\";\n\n/** A request the app sent, as a test asserts on it. */\nexport type RecordedRequest = {\n method: string;\n /** After `/api`: `/datastores`, `/datastores/dats_1`. */\n path: string;\n /** The URL's parameters other than `query`. */\n params: Record<string, string>;\n /** The `query` parameter, parsed when it is a JSON query; a text search stays a string. */\n query?: ListQuery | string;\n body?: unknown;\n /** `0` for a network error a handler answered with `HttpResponse.error()`. */\n status: number;\n};\n\nconst API_PATH = /^.*?\\/api(?=\\/)/;\n\nexport const pathOf = (url: URL) => url.pathname.replace(API_PATH, \"\");\n\nexport const parsedQuery = (raw: string | null): ListQuery | string | undefined => {\n if (raw === null) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as ListQuery)\n : raw;\n } catch {\n return raw;\n }\n};\n\nconst parsedBody = (text: string): unknown => {\n if (text === \"\") return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n};\n\n/**\n * Records every request to `/api` the server answers, from MSW's own events rather than from the\n * fake's handlers: a handler a test adds with `server.use` answers in their place, and a request\n * it answered went missing from the log, as if the page had never sent it.\n *\n * The body is read from a clone taken when the request starts, before a handler consumes it. The\n * entry is logged when the response goes out, in the order the responses do.\n *\n * @param {SetupServer} server - The server whose requests to record.\n * @param {() => RecordedRequest[]} log - The current log; a getter, as it is replaced per test.\n */\nexport const recordRequests = (server: SetupServer, log: () => RecordedRequest[]) => {\n const started = new Map<string, RecordedRequest>();\n\n server.events.on(\"request:start\", ({ request, requestId }) => {\n const url = new URL(request.url);\n if (!API_PATH.test(url.pathname)) return;\n\n const entry: RecordedRequest = {\n method: request.method.toUpperCase(),\n path: pathOf(url),\n params: Object.fromEntries([...url.searchParams].filter(([key]) => key !== \"query\")),\n query: parsedQuery(url.searchParams.get(\"query\")),\n body: undefined,\n status: 0,\n };\n started.set(requestId, entry);\n if ([\"POST\", \"PUT\", \"PATCH\"].includes(entry.method)) {\n void request\n .clone()\n .text()\n .then((text) => {\n entry.body = parsedBody(text);\n });\n }\n });\n\n server.events.on(\"response:mocked\", ({ response, requestId }) => {\n const entry = started.get(requestId);\n if (!entry) return;\n started.delete(requestId);\n entry.status = response.status;\n log().push(entry);\n });\n};\n"],"mappings":";AAkBA,IAAM,WAAW;AAEjB,IAAa,UAAU,QAAa,IAAI,SAAS,QAAQ,UAAU,EAAE;AAErE,IAAa,eAAe,QAAuD;CAC/E,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI;EACA,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAC7D,SACD;CACV,QAAQ;EACJ,OAAO;CACX;AACJ;AAEA,IAAM,cAAc,SAA0B;CAC1C,IAAI,SAAS,IAAI,OAAO,KAAA;CACxB,IAAI;EACA,OAAO,KAAK,MAAM,IAAI;CAC1B,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;;AAaA,IAAa,kBAAkB,QAAqB,QAAiC;CACjF,MAAM,0BAAU,IAAI,IAA6B;CAEjD,OAAO,OAAO,GAAG,kBAAkB,EAAE,SAAS,gBAAgB;EAC1D,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;EAC/B,IAAI,CAAC,SAAS,KAAK,IAAI,QAAQ,GAAG;EAElC,MAAM,QAAyB;GAC3B,QAAQ,QAAQ,OAAO,YAAY;GACnC,MAAM,OAAO,GAAG;GAChB,QAAQ,OAAO,YAAY,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,OAAO,CAAC;GACnF,OAAO,YAAY,IAAI,aAAa,IAAI,OAAO,CAAC;GAChD,MAAM,KAAA;GACN,QAAQ;EACZ;EACA,QAAQ,IAAI,WAAW,KAAK;EAC5B,IAAI;GAAC;GAAQ;GAAO;EAAO,CAAC,CAAC,SAAS,MAAM,MAAM,GAC9C,QACK,MAAM,CAAC,CACP,KAAK,CAAC,CACN,MAAM,SAAS;GACZ,MAAM,OAAO,WAAW,IAAI;EAChC,CAAC;CAEb,CAAC;CAED,OAAO,OAAO,GAAG,oBAAoB,EAAE,UAAU,gBAAgB;EAC7D,MAAM,QAAQ,QAAQ,IAAI,SAAS;EACnC,IAAI,CAAC,OAAO;EACZ,QAAQ,OAAO,SAAS;EACxB,MAAM,SAAS,SAAS;EACxB,IAAI,CAAC,CAAC,KAAK,KAAK;CACpB,CAAC;AACL"}
|