From e19840c4670ee7f17600c92df4207b0b5a81f096 Mon Sep 17 00:00:00 2001 From: Hugo Mendes <37412330+HugoMendes98@users.noreply.github.com> Date: Wed, 24 Jan 2024 22:06:10 +0100 Subject: [PATCH] chore: add devcontainer --- .devcontainer/Dockerfile | 10 + .devcontainer/devcontainer.json | 65 ++ .editorconfig | 3 +- .eslintignore | 1 + .eslintrc.json | 410 ++++---- .github/workflows/ci.yml | 7 +- .github/workflows/prettier-update.yml | 3 +- .github/workflows/reusable-ci.yml | 18 + .prettierignore | 2 +- .prettierrc.json | 3 +- .stylelintignore | 1 - .vscode/extensions.json | 9 +- .vscode/settings.json | 66 +- apps/backend-e2e/.eslintrc.json | 5 +- apps/backend-e2e/project.json | 4 +- .../src/backend/http/v1/auth.http.spec.ts | 60 +- .../http/v1/graph/graph-arcs.http.spec.ts | 9 +- .../http/v1/graph/graph-nodes.http.spec.ts | 19 +- .../src/backend/http/v1/graphs.http.spec.ts | 9 +- .../src/backend/http/v1/nodes.http.spec.ts | 27 +- .../src/backend/http/v1/users.http.spec.ts | 50 +- .../backend/http/v1/workflows.http.spec.ts | 8 +- .../src/support/db-e2e/db-e2e.helper.ts | 16 +- .../src/support/global/register-tsconfig.ts | 4 +- apps/backend-e2e/src/support/global/setup.ts | 26 +- .../src/support/global/teardown.ts | 4 +- .../http/clients/_lib/entity.http-client.ts | 35 +- .../support/http/clients/auth.http-client.ts | 10 +- .../support/http/clients/node.http-client.ts | 5 +- .../support/http/clients/user.http-client.ts | 5 +- .../http/clients/workflow.http-client.ts | 5 +- apps/backend/.eslintrc.json | 5 +- apps/backend/project.json | 5 +- .../src/app/_lib/app-validation.pipe.spec.ts | 11 +- .../src/app/_lib/app-validation.pipe.ts | 17 +- .../decorators/many-to-one.decorator.ts | 3 +- .../entity/entity-order.converter.spec.ts | 4 +- .../app/_lib/entity/entity-order.converter.ts | 13 +- .../entity-to-populate.coverter.spec.ts | 16 +- .../entity/entity-to-populate.coverter.ts | 10 +- .../src/app/_lib/entity/entity.service.ts | 30 +- .../src/app/_lib/entity/entity.types.ts | 18 +- .../_lib/exceptions/bad-logic.exception.ts | 5 +- apps/backend/src/app/auth/auth.controller.ts | 12 +- apps/backend/src/app/auth/auth.guard.ts | 6 +- .../backend/src/app/auth/auth.service.spec.ts | 20 +- apps/backend/src/app/auth/auth.service.ts | 4 +- .../src/app/auth/strategies/jwt.strategy.ts | 6 +- .../app/category/category.controller.spec.ts | 5 +- .../src/app/category/category.controller.ts | 16 +- .../src/app/category/category.entity.ts | 5 +- .../src/app/category/category.service.spec.ts | 38 +- .../src/app/category/category.service.ts | 5 +- .../src/app/graph/arc/graph-arc.controller.ts | 67 +- .../src/app/graph/arc/graph-arc.entity.ts | 10 +- .../app/graph/arc/graph-arc.service.spec.ts | 51 +- .../src/app/graph/arc/graph-arc.service.ts | 61 +- .../app/graph/executor/graph.executor.spec.ts | 19 +- .../graph/executor/graph.executor.state.ts | 9 +- .../src/app/graph/executor/graph.executor.ts | 19 +- .../src/app/graph/executor/graph.resolver.ts | 34 +- .../src/app/graph/graph.controller.spec.ts | 5 +- .../backend/src/app/graph/graph.controller.ts | 6 +- apps/backend/src/app/graph/graph.entity.ts | 10 +- .../src/app/graph/graph.interceptor.ts | 19 +- apps/backend/src/app/graph/graph.module.ts | 5 +- .../src/app/graph/graph.service.spec.ts | 14 +- apps/backend/src/app/graph/graph.service.ts | 6 +- .../app/graph/node/graph-node.controller.ts | 61 +- .../app/node/behaviors/node-behavior.base.ts | 6 +- .../app/node/behaviors/node-behavior.code.ts | 5 +- .../node/behaviors/node-behavior.entity.ts | 4 +- .../node/behaviors/node-behavior.function.ts | 11 +- .../node-behavior.parameter-input.ts | 11 +- .../node-behavior.parameter-output.ts | 11 +- .../node/behaviors/node-behavior.reference.ts | 5 +- .../node/behaviors/node-behavior.trigger.ts | 5 +- .../behaviors/triggers/node.trigger.entity.ts | 10 +- .../node.no-template-parameter.exception.ts | 5 +- .../node.readonly-kind-type.exception.ts | 5 +- .../node-executor.missing-input.exception.ts | 5 +- .../app/node/executor/node.executor.spec.ts | 79 +- .../src/app/node/executor/node.executor.ts | 89 +- .../node-input.readonly.exception.ts | 19 +- .../app/node/input/node-input.controller.ts | 37 +- .../src/app/node/input/node-input.entity.ts | 5 +- .../app/node/input/node-input.service.spec.ts | 78 +- .../src/app/node/input/node-input.service.ts | 30 +- .../src/app/node/input/node-input.spec.ts | 19 +- .../src/app/node/input/node-input.types.ts | 3 +- .../app/node/kind/node-kind.base.entity.ts | 5 +- apps/backend/src/app/node/node.controller.ts | 16 +- apps/backend/src/app/node/node.entity.ts | 5 +- apps/backend/src/app/node/node.interceptor.ts | 19 +- apps/backend/src/app/node/node.module.ts | 5 +- apps/backend/src/app/node/node.repository.ts | 50 +- .../backend/src/app/node/node.service.spec.ts | 88 +- apps/backend/src/app/node/node.service.ts | 114 ++- .../node-output.readonly.exception.ts | 19 +- .../app/node/output/node-output.controller.ts | 30 +- .../src/app/node/output/node-output.entity.ts | 13 +- .../node/output/node-output.service.spec.ts | 24 +- .../app/node/output/node-output.service.ts | 15 +- .../src/app/node/output/node-output.spec.ts | 19 +- apps/backend/src/app/user/user.controller.ts | 5 +- .../backend/src/app/user/user.service.spec.ts | 46 +- apps/backend/src/app/user/user.service.ts | 21 +- .../src/app/workflow/workflow.controller.ts | 15 +- .../src/app/workflow/workflow.entity.ts | 5 +- .../app/workflow/workflow.executor.spec.ts | 10 +- .../src/app/workflow/workflow.executor.ts | 5 +- .../src/app/workflow/workflow.repository.ts | 18 +- .../app/workflow/workflow.scheduler.spec.ts | 23 +- .../src/app/workflow/workflow.scheduler.ts | 4 +- .../src/app/workflow/workflow.service.spec.ts | 53 +- .../src/app/workflow/workflow.service.ts | 47 +- apps/backend/src/config.e2e.ts | 2 +- .../src/configuration/config.default.ts | 6 +- .../src/configuration/configuration.ts | 4 +- apps/backend/src/health/health.service.ts | 6 +- apps/backend/src/main.e2e.ts | 20 +- apps/backend/src/main.ts | 6 +- apps/backend/src/orm.config.ts | 16 +- .../filters/foreign-key.constraint.filter.ts | 17 +- .../orm/filters/unique.constraint.filter.ts | 17 +- apps/backend/src/orm/orm.module.ts | 6 +- .../src/orm/seeders/_lib/mocked-db.seeder.ts | 23 +- apps/backend/test/db-test/db-test.helper.ts | 8 +- .../test/support/global/logger-test.ts | 4 +- apps/backend/test/support/global/setup.ts | 20 +- apps/backend/test/support/global/teardown.ts | 4 +- apps/backend/tsconfig.app.json | 7 +- apps/backend/tsconfig.doc.json | 2 +- apps/backend/tsconfig.spec.json | 7 +- apps/backend/webpack.config.ts | 9 +- apps/frontend-e2e/.eslintrc.json | 2 +- apps/frontend-e2e/cypress.config.ts | 35 +- apps/frontend-e2e/project.json | 4 +- apps/frontend-e2e/src/e2e/auth/login.cy.ts | 23 +- apps/frontend-e2e/src/e2e/auth/profile.cy.ts | 11 +- apps/frontend-e2e/src/e2e/node/list.cy.ts | 138 +-- .../frontend-e2e/src/e2e/workflows/list.cy.ts | 111 +- .../frontend-e2e/src/e2e/workflows/view.cy.ts | 14 +- apps/frontend-e2e/src/support/commands.ts | 27 +- apps/frontend-e2e/tsconfig.json | 9 +- apps/frontend/.storybook/preview-head.html | 5 +- apps/frontend/.storybook/tsconfig.json | 10 +- apps/frontend/project.json | 9 +- .../app/_layout/header/header.component.ts | 8 +- apps/frontend/src/app/app.module.ts | 24 +- apps/frontend/src/app/app.routes.ts | 17 +- apps/frontend/src/app/auth/auth.guard.ts | 4 +- .../frontend/src/app/auth/auth.interceptor.ts | 32 +- apps/frontend/src/app/auth/auth.module.ts | 6 +- apps/frontend/src/app/auth/auth.service.ts | 21 +- .../login-card/login-card.component.html | 36 +- .../login-card.component.stories.ts | 4 +- .../login-card/login-card.component.ts | 8 +- .../auth/dialogs/profile/profile.dialog.html | 16 +- .../dialogs/profile/profile.dialog.spec.ts | 4 +- .../dialogs/profile/profile.dialog.stories.ts | 4 +- .../auth/dialogs/profile/profile.dialog.ts | 31 +- .../src/app/auth/views/login/login.view.html | 4 +- .../app/auth/views/login/login.view.spec.ts | 6 +- .../auth/views/login/login.view.stories.ts | 3 +- .../src/app/auth/views/login/login.view.ts | 22 +- .../editor/graph-editor.component.spec.ts | 5 +- .../editor/graph-editor.component.stories.ts | 16 +- .../editor/graph-editor.component.ts | 25 +- .../node-selector.component.spec.ts | 5 +- .../node-selector/node-selector.component.ts | 8 +- .../components/graph/graph.component.scss | 17 +- .../graph/graph.component.stories.ts | 16 +- .../graph/components/graph/graph.component.ts | 75 +- .../graph-node-preview.component.ts | 6 +- .../graph/rete/node/rete.node.component.html | 9 +- .../rete/node/rete.node.component.stories.ts | 4 +- .../graph-editor/graph-editor.view.stories.ts | 4 +- .../views/graph-editor/graph-editor.view.ts | 22 +- .../node.list/node.list.component.html | 26 +- .../node.list/node.list.component.spec.ts | 5 +- .../node.list/node.list.component.ts | 57 +- .../node-create/node-create.dialog.html | 12 +- .../node-create/node-create.dialog.spec.ts | 4 +- .../node-create/node-create.dialog.stories.ts | 4 +- .../dialogs/node-create/node-create.dialog.ts | 95 +- .../src/app/node/views/node/node.view.ts | 4 +- .../src/app/node/views/nodes/nodes.view.ts | 48 +- .../src/app/user/user.service.spec.ts | 12 +- .../workflow-update/workflow-update.card.html | 24 +- .../workflow-update/workflow-update.card.ts | 9 +- .../workflow.list.component.html | 8 +- .../workflow.list.component.spec.ts | 5 +- .../workflow.list.component.stories.ts | 9 +- .../workflow.list/workflow.list.component.ts | 34 +- .../workflow-create.dialog.html | 8 +- .../workflow-create.dialog.spec.ts | 9 +- .../workflow-create.dialog.stories.ts | 10 +- .../workflow-create/workflow-create.dialog.ts | 60 +- .../src/app/workflow/views/workflow.routes.ts | 4 +- .../views/workflow/workflow.view.html | 39 +- .../workflow/views/workflow/workflow.view.ts | 13 +- .../views/workflows/workflows.view.ts | 45 +- apps/frontend/src/index.html | 5 +- apps/frontend/src/styles.scss | 7 +- apps/frontend/tsconfig.app.json | 6 +- apps/frontend/tsconfig.doc.json | 8 +- apps/frontend/tsconfig.spec.json | 7 +- docker-compose.yml | 4 +- jest.preset.js | 7 +- libs/common/.eslintrc.json | 6 +- .../app/category/dtos/category.create.dto.ts | 11 +- .../category/endpoints/category.endpoint.ts | 5 +- .../graph/algorithms/graph.has-cycle.spec.ts | 19 +- .../app/graph/algorithms/graph.has-cycle.ts | 4 +- .../graph/dtos/arc/graph-arc.create.dto.ts | 11 +- .../graph/dtos/node/graph-node.create.dto.ts | 4 +- .../graph/dtos/node/graph-node.update.dto.ts | 9 +- .../graph/dtos/node/graph-node.update.spec.ts | 15 +- .../app/graph/endpoints/graph-arc.endpoint.ts | 10 +- .../graph/endpoints/graph-node.endpoint.ts | 10 +- .../src/app/graph/endpoints/graph.endpoint.ts | 5 +- .../get-adjacency-list.spec.ts | 33 +- .../transformations/get-adjacency.list.ts | 20 +- .../dtos/behaviors/node-behavior.base.dto.ts | 9 +- .../node/dtos/behaviors/node-behavior.dto.ts | 41 +- .../behaviors/node-behavior.function.dto.ts | 5 +- .../node-behavior.parameter-input.dto.ts | 7 +- .../node-behavior.parameter-output.dto.ts | 7 +- .../behaviors/node-behavior.trigger.dto.ts | 7 +- .../behaviors/node-behavior.variable.dto.ts | 11 +- .../triggers/node.trigger.base.dto.ts | 9 +- .../behaviors/triggers/node.trigger.dto.ts | 8 +- .../node/dtos/input/node-input.create.dto.ts | 5 +- .../app/node/dtos/kind/node-kind.base.dto.ts | 3 +- .../src/app/node/dtos/kind/node-kind.dto.ts | 16 +- .../kind/node-kind.template.update.dto.ts | 7 +- .../dtos/kind/node-kind.vertex.update.dto.ts | 11 +- .../src/app/node/dtos/node.create.dto.spec.ts | 56 +- .../src/app/node/dtos/node.create.dto.ts | 7 +- libs/common/src/app/node/dtos/node.dto.ts | 10 +- .../src/app/node/dtos/node.query.dto.spec.ts | 38 +- .../src/app/node/dtos/node.update.dto.spec.ts | 43 +- .../src/app/node/dtos/node.update.dto.ts | 4 +- .../dtos/output/node-output.update.dto.ts | 4 +- .../app/node/endpoints/node-input.endpoint.ts | 19 +- .../node/endpoints/node-output.endpoint.ts | 11 +- .../src/app/node/endpoints/node.endpoint.ts | 7 +- .../node/io/input/are-inputs-readonly.spec.ts | 20 +- .../app/node/io/input/are-inputs-readonly.ts | 12 +- .../src/app/node/io/node-io.functions.spec.ts | 17 +- .../src/app/node/io/node-io.functions.ts | 5 +- .../io/output/are-outputs-readonly.spec.ts | 4 +- .../node/io/output/are-outputs-readonly.ts | 4 +- .../src/app/user/dtos/user.create.dto.ts | 15 +- .../src/app/user/dtos/user.update.dto.ts | 4 +- .../src/app/user/endpoints/user.endpoint.ts | 7 +- .../app/workflow/dtos/workflow.create.dto.ts | 13 +- .../src/app/workflow/dtos/workflow.dto.ts | 8 +- .../workflow/endpoints/workflow.endpoint.ts | 9 +- libs/common/src/dtos/dto/dto.decorator.ts | 6 +- libs/common/src/dtos/dto/dto.storage.ts | 24 +- libs/common/src/dtos/dto/dto.types.ts | 10 +- libs/common/src/dtos/entity/entity.types.ts | 4 +- libs/common/src/dtos/find-query.dto.spec.ts | 19 +- libs/common/src/dtos/find-query.dto.ts | 12 +- .../find-query/find-query-order.dto.spec.ts | 12 +- .../dtos/find-query/find-query-order.dto.ts | 38 +- .../find-query/find-query-where.dto.spec.ts | 49 +- .../dtos/find-query/find-query-where.dto.ts | 67 +- .../where/where-boolean.dto.spec.ts | 23 +- .../find-query/where/where-boolean.dto.ts | 5 +- .../find-query/where/where-date.dto.spec.ts | 30 +- .../dtos/find-query/where/where-date.dto.ts | 5 +- .../find-query/where/where-number.dto.spec.ts | 23 +- .../dtos/find-query/where/where-number.dto.ts | 5 +- .../find-query/where/where-string.dto.spec.ts | 49 +- .../dtos/find-query/where/where-string.dto.ts | 5 +- libs/common/src/dtos/find-results.dto.ts | 4 +- .../src/endpoints/entity-filter.types.ts | 10 +- .../common/src/endpoints/entity-order.spec.ts | 48 +- .../src/endpoints/entity-populated.types.ts | 17 +- libs/common/src/http/http.method.spec.ts | 12 +- libs/common/src/http/http.method.ts | 6 +- .../src/http/query/http-query.decode.ts | 2 +- .../src/http/query/http-query.encode.ts | 10 +- libs/common/src/http/query/http-query.spec.ts | 12 +- .../common/src/http/query/http-query.types.ts | 5 +- libs/common/src/seeds/base.seed.ts | 97 +- libs/common/src/seeds/only-nodes.seed.ts | 28 +- libs/common/src/types/dot.path.ts | 7 +- libs/common/src/types/function.ts | 4 +- libs/common/src/types/orm.ts | 5 +- libs/common/src/types/types.ts | 4 +- libs/common/src/utils/object-fns/omit.spec.ts | 6 +- libs/common/src/utils/object-fns/pick.spec.ts | 14 +- .../validations/can-be-null.decorator.ts | 4 +- libs/common/src/validators/is-cron.spec.ts | 10 +- libs/common/src/validators/is-cron.ts | 4 +- libs/common/tsconfig.spec.json | 6 +- libs/ng/.eslintrc.json | 17 +- .../api/_lib/entity-api/entity-api.service.ts | 18 +- libs/ng/src/lib/api/api.client.ts | 22 +- libs/ng/src/lib/api/api.module.ts | 14 +- .../src/lib/api/auth-api/auth-api.service.ts | 22 +- .../api/category-api/category.api.service.ts | 5 +- .../lib/api/graph-api/node/graph-node.api.ts | 13 +- .../src/lib/api/node-api/node.api.service.ts | 6 +- .../src/lib/api/user-api/user.api.service.ts | 6 +- .../api/workflow-api/workflow.api.service.ts | 9 +- .../loading-box/loading-box.component.html | 6 +- .../lib/directives/mat-cell-def.directive.ts | 2 +- libs/ng/src/lib/forms/form-controls.types.ts | 8 +- .../list-sort-icon.component.html | 9 +- .../list-table-header.component.spec.ts | 30 +- .../list-table-header.component.ts | 19 +- libs/ng/src/lib/mat-list/list-sort.columns.ts | 9 +- .../cards/net-error/http-error.card.html | 16 +- .../cards/net-error/http-error.card.ts | 8 +- .../request-state-wrapper.component.html | 6 +- .../request-state-wrapper.component.spec.ts | 4 +- ...request-state-wrapper.component.stories.ts | 10 +- .../request-state.snapshot.spec.ts | 34 +- .../request-state/request-state.snapshot.ts | 4 +- .../request-state.subject.spec.ts | 63 +- .../request-state/request-state.subject.ts | 22 +- libs/ng/src/lib/rete/rete.connection.ts | 7 +- .../src/lib/translation/translation.module.ts | 10 +- .../translation/translation.service.spec.ts | 4 +- .../lib/translation/translation.service.ts | 40 +- libs/ng/test/setup.ts | 6 +- libs/ng/tsconfig.lib.json | 7 +- libs/ng/tsconfig.spec.json | 7 +- nx.json | 43 +- package-lock.json | 951 ------------------ package.json | 10 +- renovate.json | 10 +- tools/jest/jest-extended.ts | 6 - tools/npm/postinstall.ts | 5 +- tsconfig.base.json | 2 +- 340 files changed, 4481 insertions(+), 2632 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json mode change 120000 => 100644 .prettierignore delete mode 120000 .stylelintignore delete mode 100644 tools/jest/jest-extended.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..3ad8cc0b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,10 @@ +FROM mcr.microsoft.com/devcontainers/typescript-node:20 + +# To be able to run Cypress (no studio available in `codespace`) +# RUN apt-get install -y --no-install-recommends libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb + +# From https://github.com/devcontainers/features/tree/main/src/desktop-lite +# TODO: after enabling the desktop feature +# RUN apt install -y firefox-esr +# RUN curl -sSL https://dl.google.com/linux/direct/google-chrome-stable_current_$(dpkg --print-architecture).deb -o /tmp/chrome.deb \ +# && apt-get -y install /tmp/chrome.deb && rm /tmp/chrome.deb diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..53b512f2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,65 @@ +{ + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": [ + "Angular.ng-template", + "EditorConfig.EditorConfig", + "GitHub.vscode-pull-request-github", + "Gruntfuggly.todo-tree", + "dbaeumer.vscode-eslint", + "exiasr.hadolint", + "firsttris.vscode-jest-runner", + "formulahendry.auto-close-tag", + "formulahendry.auto-rename-tag", + "github.vscode-github-actions", + "hediet.vscode-drawio", + "king2021.vnc-extension", + "mhutchie.git-graph", + "ms-azuretools.vscode-docker", + "mtxr.sqltools", + "mtxr.sqltools-driver-pg", + "nrwl.angular-console", + "redhat.vscode-yaml", + "stylelint.vscode-stylelint", + "vivaxy.vscode-conventional-commits", + "waderyan.gitblame", + "yoavbls.pretty-ts-errors" + ], + "settings": { + // Ensure correct node version + "eslint.runtime": "/usr/local/bin/node" + } + } + }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true + }, + "ghcr.io/devcontainers/features/desktop-lite:1": { + "password": "vscode", + "vncPort": 5903 + }, + // TODO: determine better options and configure + // "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/dhoeric/features/hadolint:1": {} + }, + "forwardPorts": [5903, 6080], + "name": "Dev environment", + // Be sure to install the node_modules in and for the container before starting it, as some plugins might rely on it (e.g. eslint) + //"onCreateCommand": "rm -rf node_modules; npm install", + "portsAttributes": { + "5903": { + "label": "desktop VNC" + }, + "6080": { + "label": "desktop web" + } + }, + "postCreateCommand": "echo \"PATH=\"${PATH}:${PWD}/node_modules/.bin\"\" >> ~/.zshrc", + // docker is not nice :(. It cleans the `/tmp` dir which contains the vnc password, so it is "restarted" + "postStartCommand": "npx nx reset; killall tigervncserver" +} diff --git a/.editorconfig b/.editorconfig index 66e38e13..a22c6779 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,11 +6,12 @@ charset = utf-8 indent_size = 4 indent_style = tab insert_final_newline = true -max_line_length = 100 +max_line_length = 80 tab_width = 4 trim_trailing_whitespace = true [*.md] +max_line_length = off trim_trailing_whitespace = false [*.yml] diff --git a/.eslintignore b/.eslintignore index 2ad86774..6c3e2494 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ package-lock.json !.github/ !.github/workflows/*.yml +!.devcontainer/ !.vscode/ .vscode/* !.vscode/extensions.json diff --git a/.eslintrc.json b/.eslintrc.json index 7245d1e8..aa17b034 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,77 +2,13 @@ "extends": [ "eslint:recommended", "plugin:eslint-comments/recommended", - "plugin:storybook/recommended", "plugin:sort-decorators/strict", - "plugin:typescript-sort-keys/recommended" + "plugin:storybook/recommended" ], "overrides": [ - { - "extends": ["plugin:jsonc/base", "plugin:jsonc/recommended-with-json"], - "files": ["*.json"], - "rules": { - "jsonc/array-bracket-newline": [ - /* prettier */ - "off", - { - "minItems": 2, - "multiline": true - } - ], - "jsonc/array-element-newline": [ - /* prettier */ - "off", - "always" - ], - "jsonc/indent": ["error", "tab"], - "jsonc/key-spacing": [ - "error", - { - "afterColon": true, - "beforeColon": false, - "mode": "strict" - } - ], - "jsonc/no-comments": "warn", - "jsonc/object-curly-newline": [ - "error", - { - "consistent": true, - "minProperties": 1, - "multiline": true - } - ], - "jsonc/object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": false - } - ], - "jsonc/sort-keys": "error", - "max-len": "off", - "no-multiple-empty-lines": [ - "error", - { - "max": 0 - } - ] - } - }, - { - "files": [".eslintrc.json", ".prettierrc.json", "tsconfig.json", "tsconfig.*.json"], - "rules": { - /* OK for `rc` files */ - "jsonc/no-comments": "off" - } - }, - { - "extends": ["plugin:yml/recommended", "plugin:yml/prettier"], - "files": ["*.yml", "*.yaml"], - "parser": "yaml-eslint-parser" - }, { "extends": ["plugin:jsdoc/recommended"], - "files": ["*.ts", "*.js"], + "files": ["*.js", "*.jsx", "*.ts", "*.tsx"], "plugins": ["jsdoc"], "rules": { "@nx/enforce-module-boundaries": [ @@ -90,7 +26,10 @@ ], "jsdoc/no-undefined-types": "off", /* Hyphen is considered as a bullet point in compodoc */ - "jsdoc/require-hyphen-before-param-description": ["error", "never"], + "jsdoc/require-hyphen-before-param-description": [ + "error", + "never" + ], "jsdoc/require-param": [ "warn", { @@ -113,10 +52,44 @@ ], "sonarjs/no-all-duplicated-branches": "error", "sonarjs/no-element-overwrite": "error", + "sonarjs/no-gratuitous-expressions": "error", "sonarjs/no-identical-conditions": "error", "sonarjs/no-identical-expressions": "error", + "sonarjs/no-inverted-boolean-check": "error", "sonarjs/non-existent-operator": "error", - "sonarjs/prefer-immediate-return": "error" + "sonarjs/prefer-immediate-return": "error", + "sonarjs/prefer-object-literal": "error", + "sonarjs/prefer-single-boolean-return": "error", + "sonarjs/prefer-while": "error", + "unicorn/better-regex": "error", + "unicorn/filename-case": [ + "error", + { + "case": "kebabCase" + } + ], + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/no-array-for-each": "error", + "unicorn/no-array-push-push": "error", + "unicorn/no-await-expression-member": "error", + "unicorn/no-empty-file": "warn", + "unicorn/no-for-loop": "error", + "unicorn/no-instanceof-array": "error", + "unicorn/no-lonely-if": "error", + "unicorn/no-negated-condition": "error", + "unicorn/no-thenable": "error", + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-date-now": "error", + "unicorn/prefer-default-parameters": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-object-from-entries": "error", + "unicorn/prefer-optional-catch-binding": "error", + "unicorn/prefer-string-slice": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/throw-new-error": "error" } }, { @@ -126,11 +99,10 @@ "plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/strict" ], - "files": ["*.ts"], + "files": ["*.ts", "*.tsx"], "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "./tsconfig.base.json", - "sourceType": "module" + "project": "./tsconfig.base.json" }, "rules": { "@typescript-eslint/array-type": [ @@ -143,13 +115,19 @@ "error", { "ts-expect-error": { - "descriptionFormat": "^ - TS\\d+: .+$" + "descriptionFormat": "^ -- .+$" } } ], "@typescript-eslint/ban-types": "error", - "@typescript-eslint/consistent-generic-constructors": ["error", "constructor"], - "@typescript-eslint/consistent-indexed-object-style": ["error", "record"], + "@typescript-eslint/consistent-generic-constructors": [ + "error", + "constructor" + ], + "@typescript-eslint/consistent-indexed-object-style": [ + "error", + "record" + ], "@typescript-eslint/consistent-type-assertions": [ "error", { @@ -157,7 +135,10 @@ "objectLiteralTypeAssertions": "never" } ], - "@typescript-eslint/consistent-type-definitions": ["error", "interface"], + "@typescript-eslint/consistent-type-definitions": [ + "error", + "interface" + ], "@typescript-eslint/consistent-type-exports": "error", "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/member-delimiter-style": "error", @@ -182,9 +163,18 @@ "private-instance-field", ["public-abstract-get", "public-abstract-set"], ["public-instance-get", "public-instance-set"], - ["protected-abstract-get", "protected-abstract-set"], - ["protected-instance-get", "protected-instance-set"], - ["private-instance-get", "private-instance-set"], + [ + "protected-abstract-get", + "protected-abstract-set" + ], + [ + "protected-instance-get", + "protected-instance-set" + ], + [ + "private-instance-get", + "private-instance-set" + ], "constructor", "public-abstract-method", "protected-abstract-method", @@ -218,6 +208,7 @@ "ignoreVoidOperator": true } ], + "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-extraneous-class": [ "warn", @@ -226,12 +217,6 @@ } ], "@typescript-eslint/no-for-in-array": "error", - "@typescript-eslint/no-implicit-any-catch": [ - "error", - { - "allowExplicitAny": false - } - ], "@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-non-null-asserted-optional-chain": "error", @@ -240,6 +225,15 @@ "@typescript-eslint/no-unsafe-declaration-merging": "warn", /* Managed by "unused-imports/no-unused-vars" */ "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-use-before-define": [ + "error", + { + "classes": false, + "functions": false, + "ignoreTypeReferences": true, + "typedefs": true + } + ], "@typescript-eslint/prefer-enum-initializers": "error", "@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-includes": "warn", @@ -250,22 +244,20 @@ "@typescript-eslint/sort-type-constituents": "error", "@typescript-eslint/type-annotation-spacing": "error", "etc/no-assign-mutated-array": "error", - "etc/no-implicit-any-catch": [ - "error", - { - "allowExplicitAny": false - } - ], + "etc/prefer-less-than": "warn", "jsdoc/require-param-type": "off", - "jsdoc/require-returns-type": "off" + "jsdoc/require-returns-type": "off", + "typescript-sort-keys/string-enum": "warn" } }, { "extends": ["plugin:@nx/javascript"], - "files": ["*.js"], + "files": ["*.js", "*.jsx"], "rules": {} }, + /** Custom rules ("code") */ { + // Configuration files "files": [ "cypress.config.ts", "jest.config.ts", @@ -293,8 +285,8 @@ "env": { "jest": true }, - "extends": ["plugin:jest/recommended", "plugin:jest/style", "plugin:jest-extended/all"], - "files": ["*.spec.ts", "*.spec.js"], + "extends": ["plugin:jest/recommended", "plugin:jest/style"], + "files": ["*.spec.js", "*.spec.jsx", "*.spec.ts", "*.spec.tsx"], "plugins": ["jest"], "rules": { /* Ok for test files */ @@ -313,6 +305,109 @@ ] } }, + /** Custom rules ("non-code") */ + { + "extends": [ + "plugin:jsonc/base", + "plugin:jsonc/recommended-with-json" + ], + "files": ["*.json"], + "rules": { + "jsonc/array-bracket-newline": [ + /* prettier */ + "off", + { + "minItems": 2, + "multiline": true + } + ], + "jsonc/array-element-newline": [ + /* prettier */ + "off", + "always" + ], + "jsonc/indent": ["error", "tab"], + "jsonc/key-spacing": [ + "error", + { + "afterColon": true, + "beforeColon": false, + "mode": "strict" + } + ], + "jsonc/no-comments": "warn", + "jsonc/object-curly-newline": [ + "error", + { + "consistent": true, + "minProperties": 1, + "multiline": true + } + ], + "jsonc/object-property-newline": [ + "error", + { + "allowAllPropertiesOnSameLine": false + } + ], + "jsonc/sort-keys": "error", + "no-multiple-empty-lines": [ + "error", + { + "max": 0 + } + ] + } + }, + { + "files": [ + ".devcontainer/**/devcontainer.json", + ".eslintrc.json", + ".prettierrc.json", + ".vscode/settings.json", + "tsconfig.*.json", + "tsconfig.json" + ], + "rules": { + /* OK for `rc` files */ + "jsonc/no-comments": "off", + "jsonc/sort-array-values": [ + "warn", + { + "order": { + "type": "asc" + }, + "pathPattern": "compilerOptions.(lib|types)$|include$|files$|^exclude$|extends$|plugins$|eslint.validate|auto-close-tag.\\w+|stylelint.\\w+|vscode.extensions$" + } + ] + } + }, + { + "files": ["nx.json"], + "rules": { + "jsonc/sort-array-values": [ + "warn", + { + "order": { + "type": "asc" + }, + "pathPattern": "" + } + ] + } + }, + { + "extends": ["plugin:yml/prettier", "plugin:yml/recommended"], + "files": ["*.yaml", "*.yml"], + "parser": "yaml-eslint-parser" + }, + { + "files": ["*.sql"], + "parser": "yaml-eslint-parser", + "rules": { + /* Only prettier*/ + } + }, { "extends": ["plugin:markdownlint/recommended"], "files": ["*.md"], @@ -321,7 +416,7 @@ "markdownlint/md013": [ "error", { - "line_length": 100 + "line_length": 200 } ], "markdownlint/md033": [ @@ -335,15 +430,9 @@ } }, { - "files": ["README.md"], + "files": ["*.js", "*.ts"], "rules": { - "unicorn/filename-case": "off" - } - }, - { - "files": ["*.ts", "*.js"], - "rules": { - /* FIXME: There's a plugin that disables that rule */ + /* FIXME: There's a plugin that disables that rule (probably prettier) */ "curly": ["error", "all"] } } @@ -351,10 +440,8 @@ "plugins": [ "@cspell", "@nx", - "@shopify", "eslint-plugin-import", "etc", - "jest-extended", "prettier", "sonarjs", "sort-destructure-keys", @@ -372,33 +459,12 @@ "customWordListFile": "./tools/cspell/words.txt" } ], - "@shopify/no-useless-computed-properties": "error", - "@shopify/prefer-early-return": "error", - "arrow-parens": "off", - "brace-style": [ - /* prettier, TODO: Try to fix this */ - "off", - "1tbs", - { - "allowSingleLine": false - } - ], - "comma-dangle": "error", - "curly": ["error", "all"], - "eol-last": "error", "eqeqeq": "error", "eslint-comments/no-unused-disable": "error", "eslint-comments/require-description": [ "warn", { - "ignore": ["eslint-enable"] - } - ], - "func-style": [ - "error", - "declaration", - { - "allowArrowFunctions": true + "ignore": ["eslint", "eslint-enable"] } ], "import/first": "error", @@ -420,6 +486,11 @@ ], "newlines-between": "always", "pathGroups": [ + { + "group": "external", + "pattern": "@nna/**", + "position": "after" + }, { "group": "external", "pattern": "~/**", @@ -428,62 +499,14 @@ ] } ], - "keyword-spacing": [ - "error", - { - "after": true, - "before": true - } - ], "no-alert": "error", - "no-case-declarations": "off", "no-console": "error", "no-empty": "error", - "no-extra-semi": "error", - "no-mixed-spaces-and-tabs": "off", - "no-restricted-imports": [ - "error", - { - "patterns": [ - { - "group": ["*.spec"], - "message": "Test files must not be imported in other files (at least not app code)." - } - ] - } - ], "no-return-await": "error", - "no-trailing-spaces": "error", - "no-unused-expressions": "error", - "no-unused-labels": "error", - "no-use-before-define": "error", - "no-var": "error", - "object-curly-spacing": [ - "error", - "always", - { - "objectsInObjects": true - } - ], - "object-property-newline": [ - "warn", - { - "allowAllPropertiesOnSameLine": true - } - ], "prefer-const": "error", "prefer-rest-params": "error", "prefer-template": "error", "prettier/prettier": "error", - "quotes": [ - "error", - "double", - { - "allowTemplateLiterals": true, - "avoidEscape": true - } - ], - "semi": ["error", "always"], "sort-destructure-keys/sort-destructure-keys": "error", "sort-keys-plus/sort-keys": [ "error", @@ -493,46 +516,13 @@ } ], "sort-vars": "error", - "space-before-blocks": "error", - "space-before-function-paren": [ - "error", - { - "asyncArrow": "always", - "named": "never" - } - ], - "spaced-comment": "error", - "typescript-sort-keys/interface": "warn", - "typescript-sort-keys/string-enum": "warn", - "unicorn/filename-case": [ - "error", - { - "case": "kebabCase" - } - ], - "unicorn/no-abusive-eslint-disable": "error", - "unicorn/no-array-for-each": "error", - "unicorn/no-await-expression-member": "error", - "unicorn/no-empty-file": "error", - "unicorn/no-for-loop": "error", - "unicorn/no-lonely-if": "error", - "unicorn/no-negated-condition": "error", - "unicorn/no-thenable": "error", - "unicorn/prefer-array-find": "error", - "unicorn/prefer-array-flat": "error", - "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-array-some": "error", - "unicorn/prefer-default-parameters": "error", - "unicorn/prefer-includes": "error", - "unicorn/prefer-object-from-entries": "error", - "unicorn/prefer-optional-catch-binding": "error", - "unicorn/throw-new-error": "error", "unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [ "warn", { "args": "after-used", - "argsIgnorePattern": "^_" + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_\\d*" } ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39816b99..b4183312 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,14 +16,9 @@ concurrency: jobs: checks: - if: contains(github.event.pull_request.labels.*.name, 'renovate') != true uses: ./.github/workflows/reusable-ci.yml secrets: NX_TOKEN: ${{ secrets.NX_TOKEN }} - - checks_renovate: - if: contains(github.event.pull_request.labels.*.name, 'renovate') - uses: ./.github/workflows/reusable-ci.yml with: # NX token not used to limit its usage (credits) - NX_NO_CLOUD: true + NX_NO_CLOUD: ${{ contains(github.event.pull_request.labels.*.name, 'renovate') == true }} diff --git a/.github/workflows/prettier-update.yml b/.github/workflows/prettier-update.yml index fc4e1de7..58d460ba 100644 --- a/.github/workflows/prettier-update.yml +++ b/.github/workflows/prettier-update.yml @@ -11,6 +11,7 @@ on: branches: [master] paths: - "package.json" + - "package-lock.json" concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.ref }} @@ -36,7 +37,7 @@ jobs: - name: Check if prettier was changed as part of the latest commit on the PR id: prettier-package-check run: | - git diff HEAD~1 -G"prettier" --exit-code package.json && echo "prettier unchanged" || echo "::set-output name=was-changed::true" + git diff HEAD~1 -G"prettier" --exit-code package.json package-lock.json && echo "prettier unchanged" || echo "::set-output name=was-changed::true" - name: Run prettier formatting if prettier was changed and commit the results if: ${{ steps.prettier-package-check.outputs.was-changed == 'true' }} diff --git a/.github/workflows/reusable-ci.yml b/.github/workflows/reusable-ci.yml index 4d465997..d2440081 100644 --- a/.github/workflows/reusable-ci.yml +++ b/.github/workflows/reusable-ci.yml @@ -19,6 +19,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: nrwl/nx-set-shas@v4 + with: + main-branch-name: master + - run: git branch --track master origin/master - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: @@ -42,6 +48,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: nrwl/nx-set-shas@v4 + with: + main-branch-name: master + - run: git branch --track master origin/master - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: @@ -89,6 +101,12 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: nrwl/nx-set-shas@v4 + with: + main-branch-name: master + - run: git branch --track master origin/master - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: diff --git a/.prettierignore b/.prettierignore deleted file mode 120000 index f62e2b43..00000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -.eslintignore \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..54641bb1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +# Ignored: Prettier is called from eslint diff --git a/.prettierrc.json b/.prettierrc.json index f5422aef..16f376dc 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -31,7 +31,8 @@ "files": "*.yml", "options": { "parser": "yaml", - "tabWidth": 2 + "tabWidth": 2, + "useTabs": false } } ], diff --git a/.stylelintignore b/.stylelintignore deleted file mode 120000 index f62e2b43..00000000 --- a/.stylelintignore +++ /dev/null @@ -1 +0,0 @@ -.eslintignore \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 754ab770..337f4742 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,3 @@ { - "recommendations": [ - "nrwl.angular-console", - "esbenp.prettier-vscode", - "firsttris.vscode-jest-runner", - "dbaeumer.vscode-eslint", - "emeraldwalk.runonsave", - "stylelint.vscode-stylelint" - ] + "recommendations": ["ms-vscode-remote.remote-containers"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 60051fe8..2b17c9b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,17 +1,57 @@ { + "auto-close-tag.activationOnLanguage": [ + "HTML (EEx)", + "HTML (Eex)", + "ejs", + "markdown", + "xml" + ], "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, - "emeraldwalk.runonsave": { - "commands": [ - { - "cmd": "npx eslint --fix ${file}", - "match": ".*\\.[ts|js|json|md|html|yml|sql]" - }, - { - "cmd": "npx stylelint --fix ${file}", - "match": ".*\\.[css|scss]" - } - ] - } + "eslint.onIgnoredFiles": "warn", + "eslint.validate": [ + "html", + "javascript", + "javascriptreact", + "json", + "markdown", + "typescript", + "typescriptreact", + "vue", + "yaml" + ], + "files.associations": { + // Supposition: some files are ignored (e.g.: tsconfig.json) in the eslint plugin + // Doing this, "force" them to be considered + "*.json": "json", + "*.yaml": "yaml", + "*.yml": "yaml" + }, + "sqltools.autoOpenSessionFiles": false, + "sqltools.connections": [ + { + "database": "nna", + "driver": "PostgreSQL", + "name": "DB", + "password": "M1fmx9Tef2dq5UXN", + "port": 5432, + "previewLimit": 50, + "server": "localhost", + "username": "nna" + }, + { + "database": "_db-test", + "driver": "PostgreSQL", + "name": "DB test", + "password": "_db-test", + "port": 32321, + "previewLimit": 50, + "server": "localhost", + "username": "_db-test" + } + ], + "stylelint.enable": true, + "stylelint.snippet": ["css", "scss"], + "stylelint.validate": ["css", "scss"] } diff --git a/apps/backend-e2e/.eslintrc.json b/apps/backend-e2e/.eslintrc.json index ede7e125..22d50008 100644 --- a/apps/backend-e2e/.eslintrc.json +++ b/apps/backend-e2e/.eslintrc.json @@ -12,7 +12,10 @@ } }, { - "files": ["./src/support/global/setup.ts", "./src/support/global/teardown.ts"], + "files": [ + "./src/support/global/setup.ts", + "./src/support/global/teardown.ts" + ], "rules": { "import/no-default-export": "off", "jsdoc/require-jsdoc": "off" diff --git a/apps/backend-e2e/project.json b/apps/backend-e2e/project.json index bc2066d2..d28f16a3 100644 --- a/apps/backend-e2e/project.json +++ b/apps/backend-e2e/project.json @@ -14,7 +14,9 @@ "lint": { "executor": "@nx/linter:eslint", "options": { - "lintFilePatterns": ["apps/backend-e2e/**/*.{ts,js,md,json,yml}"] + "lintFilePatterns": [ + "apps/backend-e2e/**/*.{ts,js,md,json,yml}" + ] }, "outputs": ["{options.outputFile}"] } diff --git a/apps/backend-e2e/src/backend/http/v1/auth.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/auth.http.spec.ts index eb417042..5cf698bd 100644 --- a/apps/backend-e2e/src/backend/http/v1/auth.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/auth.http.spec.ts @@ -13,7 +13,9 @@ describe("Backend HTTP Auth", () => { const client = new AuthHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const { users } = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const { users } = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof BASE_SEED + >; beforeAll(() => dbHelper.refresh()); @@ -22,21 +24,23 @@ describe("Backend HTTP Auth", () => { const timeoutMs = configE2e.authentication.timeout * 1000; for (const user of users) { - const now = new Date().getTime(); + const now = Date.now(); const { access_token, expires_at } = await client.login({ email: user.email, password: user.password } satisfies AuthLoginDto); - expect(access_token).toBeString(); - expect(expires_at).toBeGreaterThanOrEqual(now + timeoutMs - 1500); + expect(typeof access_token === "string").toBe(true); + expect(expires_at).toBeGreaterThanOrEqual( + now + timeoutMs - 1500 + ); expect(expires_at).toBeLessThanOrEqual(now + timeoutMs + 1500); } }); it("should return a `Set-Cookie` header when logging with the cookie option", async () => { const [{ email, password }] = users; - const now = new Date().getTime(); + const now = Date.now(); const { data: { access_token }, @@ -54,7 +58,6 @@ describe("Backend HTTP Auth", () => { expect(cookie[authOptions.cookies.name]).toBe(access_token); expect(cookie).toHaveProperty("Expires"); - expect(cookie.Expires).toBeDateString(); const expires_at = new Date(cookie.Expires).getTime(); const timeoutMs = configE2e.authentication.timeout * 1000; @@ -77,14 +80,20 @@ describe("Backend HTTP Auth", () => { it("should fail when the email is missing", async () => { const { status } = await client - .loginResponse({ password: "password" } satisfies Omit) + .loginResponse({ password: "password" } satisfies Omit< + AuthLoginDto, + "email" + >) .catch(({ response }: AxiosError) => response!); expect(status).toBe(HttpStatusCode.BadRequest); }); it("should fail when the password is missing", async () => { const { status } = await client - .loginResponse({ email: "a@b.cd" } satisfies Omit) + .loginResponse({ email: "a@b.cd" } satisfies Omit< + AuthLoginDto, + "password" + >) .catch(({ response }: AxiosError) => response!); expect(status).toBe(HttpStatusCode.BadRequest); }); @@ -112,26 +121,31 @@ describe("Backend HTTP Auth", () => { }); it("should refresh with authentication set in header", async () => { - const { access_token: token, expires_at: expires_before } = await client.login( - users[0] satisfies AuthLoginDto - ); + const { access_token: token, expires_at: expires_before } = + await client.login(users[0] satisfies AuthLoginDto); // So the expires_at time is not the same await sleep(); - const { access_token, expires_at } = await client.refresh(undefined, { - headers: new AxiosHeaders().setAuthorization(`Bearer ${token}`) - }); + const { access_token, expires_at } = await client.refresh( + undefined, + { + headers: new AxiosHeaders().setAuthorization( + `Bearer ${token}` + ) + } + ); expect(access_token).not.toBe(token); expect(expires_at).toBeGreaterThan(expires_before); }); it("should refresh with authentication set in cookie", async () => { - const { access_token: token, expires_at: expires_before } = await client.login({ - ...users[0], - cookie: true - } satisfies AuthLoginDto); + const { access_token: token, expires_at: expires_before } = + await client.login({ + ...users[0], + cookie: true + } satisfies AuthLoginDto); // So the expires_at time is not the same await sleep(); @@ -164,17 +178,21 @@ describe("Backend HTTP Auth", () => { const cookie = cookieParser(headers["set-cookie"]![0]); expect(cookie).toHaveProperty(authOptions.cookies.name); - expect(cookie[authOptions.cookies.name]).toBeEmpty(); + expect(cookie[authOptions.cookies.name]).toBe(""); }); }); describe("GetProfile", () => { it("should get profile of connected user", async () => { for (const user of users) { - const { access_token } = await client.login(user satisfies AuthLoginDto); + const { access_token } = await client.login( + user satisfies AuthLoginDto + ); const profile = await client.getProfile({ - headers: new AxiosHeaders().setAuthorization(`Bearer ${access_token}`) + headers: new AxiosHeaders().setAuthorization( + `Bearer ${access_token}` + ) }); expect(profile).toStrictEqual(omit(user, ["password"])); } diff --git a/apps/backend-e2e/src/backend/http/v1/graph/graph-arcs.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/graph/graph-arcs.http.spec.ts index d5bf8f21..8d850e15 100644 --- a/apps/backend-e2e/src/backend/http/v1/graph/graph-arcs.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/graph/graph-arcs.http.spec.ts @@ -11,7 +11,9 @@ describe("Backend HTTP GraphArcs", () => { const graphClient = new GraphHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof BASE_SEED + >; const { arcs, graphs, nodes } = db.graph; const [graphRef] = graphs; @@ -44,7 +46,10 @@ describe("Backend HTTP GraphArcs", () => { data, pagination: { total } } = await client.findMany({ - params: { limit, order: [{ _id: "asc" }] } satisfies GraphArcQueryDto + params: { + limit, + order: [{ _id: "asc" }] + } satisfies GraphArcQueryDto }); expect(data).toHaveLength(limit); diff --git a/apps/backend-e2e/src/backend/http/v1/graph/graph-nodes.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/graph/graph-nodes.http.spec.ts index b19b33be..80e2da33 100644 --- a/apps/backend-e2e/src/backend/http/v1/graph/graph-nodes.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/graph/graph-nodes.http.spec.ts @@ -12,12 +12,18 @@ describe("Backend HTTP GraphNodes", () => { const graphClient = new GraphHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof dbHelper.db + >; const { graphs, nodes } = db.graph; const [graphRef] = graphs; const nodesRef = nodes - .filter(({ kind }) => kind.type === NodeKindType.VERTEX && kind.__graph === graphRef._id) + .filter( + ({ kind }) => + kind.type === NodeKindType.VERTEX && + kind.__graph === graphRef._id + ) .map(node => omit(node, ["__categories"])); const client = graphClient.forNodes(graphRef._id); @@ -39,7 +45,10 @@ describe("Backend HTTP GraphNodes", () => { data, pagination: { total } } = await client.findMany({ - params: { limit, order: [{ _id: "asc" }] } satisfies NodeQueryDto + params: { + limit, + order: [{ _id: "asc" }] + } satisfies NodeQueryDto }); expect(data).toHaveLength(limit); @@ -69,7 +78,9 @@ describe("Backend HTTP GraphNodes", () => { it("should fail when getting one from another graph", async () => { const node = nodes.find( - ({ kind }) => kind.type === NodeKindType.VERTEX && kind.__graph !== graphRef._id + ({ kind }) => + kind.type === NodeKindType.VERTEX && + kind.__graph !== graphRef._id ); expect(node).toBeDefined(); diff --git a/apps/backend-e2e/src/backend/http/v1/graphs.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/graphs.http.spec.ts index c29b3b8b..aa26c43a 100644 --- a/apps/backend-e2e/src/backend/http/v1/graphs.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/graphs.http.spec.ts @@ -10,7 +10,9 @@ describe("Backend HTTP Graphs", () => { const client = new GraphHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof BASE_SEED + >; const { graphs } = db.graph; @@ -32,7 +34,10 @@ describe("Backend HTTP Graphs", () => { data, pagination: { total } } = await client.findMany({ - params: { limit, order: [{ _id: "asc" }] } satisfies GraphQueryDto + params: { + limit, + order: [{ _id: "asc" }] + } satisfies GraphQueryDto }); expect(data).toHaveLength(limit); diff --git a/apps/backend-e2e/src/backend/http/v1/nodes.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/nodes.http.spec.ts index c166a844..053af138 100644 --- a/apps/backend-e2e/src/backend/http/v1/nodes.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/nodes.http.spec.ts @@ -1,6 +1,10 @@ import { DbE2eHelper } from "~/app/backend/e2e/db-e2e/db-e2e.helper"; import { NodeHttpClient } from "~/app/backend/e2e/http/clients/node.http-client"; -import { NodeCreateDto, NodeQueryDto, NodeUpdateDto } from "~/lib/common/app/node/dtos"; +import { + NodeCreateDto, + NodeQueryDto, + NodeUpdateDto +} from "~/lib/common/app/node/dtos"; import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; import { NodeKindVertexDto } from "~/lib/common/app/node/dtos/kind"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; @@ -23,7 +27,9 @@ describe("Backend HTTP Nodes", () => { describe("GET", () => { it("should filter by kind type", async () => { const type = NodeKindType.TEMPLATE; - const expected = db.graph.nodes.filter(({ kind }) => kind.type === type); + const expected = db.graph.nodes.filter( + ({ kind }) => kind.type === type + ); const { data, pagination } = await client.findMany({ params: { where: { kind: { type } } } satisfies NodeQueryDto @@ -37,7 +43,7 @@ describe("Backend HTTP Nodes", () => { }); it("should filter by number property", async () => { - const expected = db.graph.nodes.filter(({ _id }) => _id >= 10); + const expected = db.graph.nodes.filter(({ _id }) => 10 <= _id); const { data } = await client.findMany({ params: { where: { _id: { $gte: 10 } } } satisfies NodeQueryDto @@ -51,7 +57,9 @@ describe("Backend HTTP Nodes", () => { it("should filter by boolean property", async () => { const type = NodeKindType.TEMPLATE; - const expected = db.graph.nodes.filter(({ kind }) => kind.type === type && kind.active); + const expected = db.graph.nodes.filter( + ({ kind }) => kind.type === type && kind.active + ); const { data } = await client.findMany({ params: { @@ -73,7 +81,11 @@ describe("Backend HTTP Nodes", () => { for (const toCreate of [ { behavior: { type: NodeBehaviorType.VARIABLE, value: 1 }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "-new node1" }, { @@ -98,7 +110,10 @@ describe("Backend HTTP Nodes", () => { const node = db.graph.nodes[0]; const toUpdate = { - kind: { position: { x: 250, y: 250 }, type: NodeKindType.VERTEX } + kind: { + position: { x: 250, y: 250 }, + type: NodeKindType.VERTEX + } } as const satisfies NodeUpdateDto; const updated = await client.update(node._id, toUpdate); diff --git a/apps/backend-e2e/src/backend/http/v1/users.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/users.http.spec.ts index 9cf04f48..d3a2732c 100644 --- a/apps/backend-e2e/src/backend/http/v1/users.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/users.http.spec.ts @@ -1,7 +1,11 @@ import { AxiosError, HttpStatusCode } from "axios"; import { Jsonify } from "type-fest"; import { UserHttpClient } from "~/app/backend/e2e/http/clients"; -import { UserDto, UserQueryDto, UserUpdateDto } from "~/lib/common/app/user/dtos"; +import { + UserDto, + UserQueryDto, + UserUpdateDto +} from "~/lib/common/app/user/dtos"; import { USERS_ENDPOINT_PREFIX } from "~/lib/common/app/user/endpoints"; import { EntityOrder } from "~/lib/common/endpoints"; import { omit } from "~/lib/common/utils/object-fns"; @@ -12,7 +16,9 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { const client = new UserHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof dbHelper.db + >; const { users } = db; const [user] = users; @@ -48,7 +54,9 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { const { data, pagination: { total } - } = await client.findMany({ params: { limit: 0 } satisfies UserQueryDto }); + } = await client.findMany({ + params: { limit: 0 } satisfies UserQueryDto + }); expect(data).toHaveLength(0); expect(total).toBe(sorted.length); @@ -130,7 +138,9 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { const { data: ordered1 } = await client.findMany({ params: { order: [{ _id: "asc" }] } satisfies UserQueryDto }); - expect(ordered1).toStrictEqual(sorted.map(user => omit(user, ["password"]))); + expect(ordered1).toStrictEqual( + sorted.map(user => omit(user, ["password"])) + ); const { data: ordered2 } = await client.findMany({ params: { order: [{ _id: "desc" }] } satisfies UserQueryDto @@ -165,7 +175,9 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { } satisfies UserQueryDto }); - expect(actual).toStrictEqual(expected.map(user => omit(user, ["password"]))); + expect(actual).toStrictEqual( + expected.map(user => omit(user, ["password"])) + ); }); it("should filter with `$or`", async () => { @@ -173,16 +185,21 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { const filterEmail = sorted[1].email; const expected = sorted.filter( - ({ _id, email }) => _id === filterId || email === filterEmail + ({ _id, email }) => + _id === filterId || email === filterEmail ); const { data: actual } = await client.findMany({ params: { order: [{ _id: "asc" }], - where: { $or: [{ _id: filterId }, { email: filterEmail }] } + where: { + $or: [{ _id: filterId }, { email: filterEmail }] + } } satisfies UserQueryDto }); - expect(actual).toStrictEqual(expected.map(user => omit(user, ["password"]))); + expect(actual).toStrictEqual( + expected.map(user => omit(user, ["password"])) + ); }); }); }); @@ -193,10 +210,12 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { pagination: { total: beforeTotal } } = await client.findMany(); - const toUpdate: UserUpdateDto = { firstname: `${user.firstname || "abc"}.123` }; + const toUpdate: UserUpdateDto = { + firstname: `${user.firstname || "abc"}.123` + }; const updated = await client.update(user._id, toUpdate); expect(updated.firstname).toBe(toUpdate.firstname); - expect(updated._updated_at > user._updated_at).toBeTrue(); + expect(user._updated_at < updated._updated_at).toBe(true); const { data: after, @@ -210,7 +229,10 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { }); it("should not be able to update a user's email", async () => { - const toUpdate = { email: `abc.${user.email}` } satisfies Pick; + const toUpdate = { email: `abc.${user.email}` } satisfies Pick< + UserDto, + "email" + >; const updated = await client.update(user._id, toUpdate); expect(updated.email).toBe(user.email); @@ -221,7 +243,9 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { const { _id } = users.find(({ _id }) => _id !== user._id)!; const response = await client - .updateResponse(_id, { firstname: "abc123" } satisfies UserUpdateDto) + .updateResponse(_id, { + firstname: "abc123" + } satisfies UserUpdateDto) .catch(({ response }: AxiosError) => response!); expect(response.status).toBe(404); }); @@ -249,7 +273,7 @@ describe(`Backend HTTP ${USERS_ENDPOINT_PREFIX}`, () => { pagination: { total: afterTotal } } = await client.findMany(); expect(afterTotal).toBe(beforeTotal - 1); - expect(after.some(({ _id }) => _id === deleted._id)).toBeFalse(); + expect(after.some(({ _id }) => _id === deleted._id)).toBe(false); }); it("should not be able to delete another user (404)", async () => { diff --git a/apps/backend-e2e/src/backend/http/v1/workflows.http.spec.ts b/apps/backend-e2e/src/backend/http/v1/workflows.http.spec.ts index a1b47c2d..2b14a5b3 100644 --- a/apps/backend-e2e/src/backend/http/v1/workflows.http.spec.ts +++ b/apps/backend-e2e/src/backend/http/v1/workflows.http.spec.ts @@ -10,7 +10,9 @@ describe("Backend HTTP Graphs", () => { const client = new WorkflowHttpClient(); const dbHelper = DbE2eHelper.getHelper("base"); - const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify; + const db = JSON.parse(JSON.stringify(dbHelper.db)) as Jsonify< + typeof BASE_SEED + >; beforeAll(async () => { const [{ email, password }] = db.users; @@ -25,7 +27,9 @@ describe("Backend HTTP Graphs", () => { it("should order by boolean properties", async () => { const response = await client .findManyResponse({ - params: { order: [{ active: "desc" }] } satisfies WorkflowQueryDto + params: { + order: [{ active: "desc" }] + } satisfies WorkflowQueryDto }) .catch(({ response }: AxiosError) => response!); diff --git a/apps/backend-e2e/src/support/db-e2e/db-e2e.helper.ts b/apps/backend-e2e/src/support/db-e2e/db-e2e.helper.ts index bbcd89ce..c30e99b7 100644 --- a/apps/backend-e2e/src/support/db-e2e/db-e2e.helper.ts +++ b/apps/backend-e2e/src/support/db-e2e/db-e2e.helper.ts @@ -2,9 +2,17 @@ import { Singleton } from "@heap-code/singleton"; import axios from "axios"; import { config as ConfigE2e } from "~/app/backend/app/config.e2e"; import { DbTestHelper, DbTestSample } from "~/app/backend/test/db-test"; -import { BASE_SEED, EMPTY_SEED, MockSeed, ONLY_NODES_SEED } from "~/lib/common/seeds"; +import { + BASE_SEED, + EMPTY_SEED, + MockSeed, + ONLY_NODES_SEED +} from "~/lib/common/seeds"; -import { E2E_ENDPOINT_DB_SEEDING, E2eEndpointDbSeedingBody } from "../e2e.endpoints"; +import { + E2E_ENDPOINT_DB_SEEDING, + E2eEndpointDbSeedingBody +} from "../e2e.endpoints"; const { name, port } = ConfigE2e.host; const baseURL = `http://${name}:${port}`; @@ -15,7 +23,9 @@ const dbSamples: Record = { "only-nodes": ONLY_NODES_SEED }; -export class DbE2eHelper implements Omit { +export class DbE2eHelper + implements Omit +{ private static readonly helpers = new Map( Object.entries(dbSamples).map(([sample, db]) => [ sample, diff --git a/apps/backend-e2e/src/support/global/register-tsconfig.ts b/apps/backend-e2e/src/support/global/register-tsconfig.ts index 455646d2..852d2fe2 100644 --- a/apps/backend-e2e/src/support/global/register-tsconfig.ts +++ b/apps/backend-e2e/src/support/global/register-tsconfig.ts @@ -7,7 +7,9 @@ import { register } from "tsconfig-paths"; // So this file forces to use a tsconfig, at least for the paths. const cwd = path.join(__dirname, "../../../../.."); -const tsConfigContent = fs.readFileSync(path.join(cwd, "tsconfig.e2e.json")).toString(); +const tsConfigContent = fs + .readFileSync(path.join(cwd, "tsconfig.e2e.json")) + .toString(); const tsConfig = commentJson.parse( tsConfigContent ) as unknown as typeof import("../../../../../tsconfig.e2e.json"); diff --git a/apps/backend-e2e/src/support/global/setup.ts b/apps/backend-e2e/src/support/global/setup.ts index 6756f6c6..02c73cf8 100644 --- a/apps/backend-e2e/src/support/global/setup.ts +++ b/apps/backend-e2e/src/support/global/setup.ts @@ -29,10 +29,14 @@ export async function globalSetup(params?: GlobalSetupParams) { throw new Error("The app was already set when setting up!"); } } else { - const command = spawn("nx", ["run", "backend:serve:test-e2e", "--no-inspect"], { - cwd: path.join(__dirname, "../../../../../"), - detached: false - }); + const command = spawn( + "nx", + ["run", "backend:serve:test-e2e", "--no-inspect"], + { + cwd: path.join(__dirname, "../../../../../"), + detached: false + } + ); app = { command, kill: () => { @@ -57,11 +61,19 @@ export async function globalSetup(params?: GlobalSetupParams) { } }; - command.stdout.on("data", (data: string) => logger.log("backend |", `${data}`)); - command.stderr.on("data", (data: string) => logger.error("backend |", `${data}`)); + command.stdout.on("data", (data: string) => + logger.log("backend |", `${data}`) + ); + command.stderr.on("data", (data: string) => + logger.error("backend |", `${data}`) + ); logger.log("Waiting for backend"); - await waitFor({ host: config.host.name, port: config.host.port, timeout: 30000 }); + await waitFor({ + host: config.host.name, + port: config.host.port, + timeout: 30000 + }); } (globalThis as unknown as GlobalThis).jest_config.backend = app; diff --git a/apps/backend-e2e/src/support/global/teardown.ts b/apps/backend-e2e/src/support/global/teardown.ts index 7e5ca80c..4948a87b 100644 --- a/apps/backend-e2e/src/support/global/teardown.ts +++ b/apps/backend-e2e/src/support/global/teardown.ts @@ -32,7 +32,9 @@ export async function globalTeardown(params?: GlobalTeardownParams) { `SIGTERM` ]) { process.on(eventType, () => { - (globalThis as unknown as GlobalThis).jest_config.backend.kill(); + ( + globalThis as unknown as GlobalThis + ).jest_config.backend.kill(); }); } } else { diff --git a/apps/backend-e2e/src/support/http/clients/_lib/entity.http-client.ts b/apps/backend-e2e/src/support/http/clients/_lib/entity.http-client.ts index 5933193a..646c5dc1 100644 --- a/apps/backend-e2e/src/support/http/clients/_lib/entity.http-client.ts +++ b/apps/backend-e2e/src/support/http/clients/_lib/entity.http-client.ts @@ -26,16 +26,25 @@ export abstract class EntityHttpClient { public abstract getEndpoint(): string; public findManyResponse(...params: DropFirst>) { - return this.client.get>(this.getEndpoint(), ...params); + return this.client.get>( + this.getEndpoint(), + ...params + ); } public findMany(...params: DropFirst>) { return this.findManyResponse(...params).then(({ data }) => data); } - public findOneResponse(id: EntityId, ...params: DropFirst>) { + public findOneResponse( + id: EntityId, + ...params: DropFirst> + ) { return this.client.get(`${this.getEndpoint()}/${id}`, ...params); } - public findOne(id: EntityId, ...params: DropFirst>) { + public findOne( + id: EntityId, + ...params: DropFirst> + ) { return this.findOneResponse(id, ...params).then(({ data }) => data); } @@ -46,17 +55,29 @@ export abstract class EntityHttpClient { return this.createResponse(...params).then(({ data }) => data); } - public updateResponse(id: EntityId, ...params: DropFirst>) { + public updateResponse( + id: EntityId, + ...params: DropFirst> + ) { return this.client.patch(`${this.getEndpoint()}/${id}`, ...params); } - public update(id: EntityId, ...params: DropFirst>) { + public update( + id: EntityId, + ...params: DropFirst> + ) { return this.updateResponse(id, ...params).then(({ data }) => data); } - public deleteResponse(id: EntityId, ...params: DropFirst>) { + public deleteResponse( + id: EntityId, + ...params: DropFirst> + ) { return this.client.delete(`${this.getEndpoint()}/${id}`, ...params); } - public delete(id: EntityId, ...params: DropFirst>) { + public delete( + id: EntityId, + ...params: DropFirst> + ) { return this.deleteResponse(id, ...params).then(({ data }) => data); } diff --git a/apps/backend-e2e/src/support/http/clients/auth.http-client.ts b/apps/backend-e2e/src/support/http/clients/auth.http-client.ts index cd98f89c..5eca0915 100644 --- a/apps/backend-e2e/src/support/http/clients/auth.http-client.ts +++ b/apps/backend-e2e/src/support/http/clients/auth.http-client.ts @@ -1,12 +1,18 @@ import axios, { Axios } from "axios"; import { AuthSuccessDto } from "~/lib/common/app/auth/dtos"; -import { AUTH_ENDPOINT_PREFIX, AuthEndpoints } from "~/lib/common/app/auth/endpoints"; +import { + AUTH_ENDPOINT_PREFIX, + AuthEndpoints +} from "~/lib/common/app/auth/endpoints"; import { UserDto } from "~/lib/common/app/user/dtos"; import { DropFirst } from "~/lib/common/types"; export class AuthHttpClient { public getProfileResponse(...params: DropFirst>) { - return axios.get(`${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.PROFILE}`, ...params); + return axios.get( + `${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.PROFILE}`, + ...params + ); } public getProfile(...params: DropFirst>) { return this.getProfileResponse(...params).then(({ data }) => data); diff --git a/apps/backend-e2e/src/support/http/clients/node.http-client.ts b/apps/backend-e2e/src/support/http/clients/node.http-client.ts index a81f2364..eefa72ee 100644 --- a/apps/backend-e2e/src/support/http/clients/node.http-client.ts +++ b/apps/backend-e2e/src/support/http/clients/node.http-client.ts @@ -1,4 +1,7 @@ -import { NodeJSON, NODES_ENDPOINT_PREFIX } from "~/lib/common/app/node/endpoints"; +import { + NodeJSON, + NODES_ENDPOINT_PREFIX +} from "~/lib/common/app/node/endpoints"; import { EntityHttpClient } from "./_lib/entity.http-client"; diff --git a/apps/backend-e2e/src/support/http/clients/user.http-client.ts b/apps/backend-e2e/src/support/http/clients/user.http-client.ts index 3c057872..d8b85779 100644 --- a/apps/backend-e2e/src/support/http/clients/user.http-client.ts +++ b/apps/backend-e2e/src/support/http/clients/user.http-client.ts @@ -1,4 +1,7 @@ -import { UserJSON, USERS_ENDPOINT_PREFIX } from "~/lib/common/app/user/endpoints"; +import { + UserJSON, + USERS_ENDPOINT_PREFIX +} from "~/lib/common/app/user/endpoints"; import { EntityHttpClient } from "./_lib/entity.http-client"; diff --git a/apps/backend-e2e/src/support/http/clients/workflow.http-client.ts b/apps/backend-e2e/src/support/http/clients/workflow.http-client.ts index dd799050..64f592b6 100644 --- a/apps/backend-e2e/src/support/http/clients/workflow.http-client.ts +++ b/apps/backend-e2e/src/support/http/clients/workflow.http-client.ts @@ -1,4 +1,7 @@ -import { WorkflowJSON, WORKFLOWS_ENDPOINT_PREFIX } from "~/lib/common/app/workflow/endpoints"; +import { + WorkflowJSON, + WORKFLOWS_ENDPOINT_PREFIX +} from "~/lib/common/app/workflow/endpoints"; import { EntityHttpClient } from "./_lib/entity.http-client"; diff --git a/apps/backend/.eslintrc.json b/apps/backend/.eslintrc.json index b8e3dc2e..99e61a99 100644 --- a/apps/backend/.eslintrc.json +++ b/apps/backend/.eslintrc.json @@ -21,7 +21,10 @@ } }, { - "files": ["./test/support/global/setup.ts", "./test/support/global/teardown.ts"], + "files": [ + "./test/support/global/setup.ts", + "./test/support/global/teardown.ts" + ], "rules": { "import/no-default-export": "off", "jsdoc/require-jsdoc": "off" diff --git a/apps/backend/project.json b/apps/backend/project.json index 16c18b4f..2ebfb3ca 100644 --- a/apps/backend/project.json +++ b/apps/backend/project.json @@ -94,8 +94,6 @@ "test": { "configurations": { "ci": { - "ci": true, - "codeCoverage": true, "coverageDirectory": "./coverage/apps/backend", "coverageReporters": ["text", "json-summary"] } @@ -103,7 +101,8 @@ "executor": "@nx/jest:jest", "options": { "jestConfig": "apps/backend/jest.config.ts", - "passWithNoTests": true + "passWithNoTests": false, + "runInBand": true }, "outputs": ["{workspaceRoot}/coverage/{projectRoot}"] } diff --git a/apps/backend/src/app/_lib/app-validation.pipe.spec.ts b/apps/backend/src/app/_lib/app-validation.pipe.spec.ts index b493e882..c70cde24 100644 --- a/apps/backend/src/app/_lib/app-validation.pipe.spec.ts +++ b/apps/backend/src/app/_lib/app-validation.pipe.spec.ts @@ -21,10 +21,13 @@ describe("AppValidationPipe", () => { describe("Implicit conversion", () => { it("should not convert when the value is a body", async () => { await expect( - pipe.transform({ limit: "5" as unknown as number } satisfies FindQueryDto, { - metatype: FindQueryDto, - type: "body" - }) + pipe.transform( + { limit: "5" as unknown as number } satisfies FindQueryDto, + { + metatype: FindQueryDto, + type: "body" + } + ) ).rejects.toThrow(BadRequestException); }); diff --git a/apps/backend/src/app/_lib/app-validation.pipe.ts b/apps/backend/src/app/_lib/app-validation.pipe.ts index 191abfa5..da8d310a 100644 --- a/apps/backend/src/app/_lib/app-validation.pipe.ts +++ b/apps/backend/src/app/_lib/app-validation.pipe.ts @@ -1,4 +1,9 @@ -import { ArgumentMetadata, Injectable, ValidationError, ValidationPipe } from "@nestjs/common"; +import { + ArgumentMetadata, + Injectable, + ValidationError, + ValidationPipe +} from "@nestjs/common"; import { ValidationPipeOptions } from "@nestjs/common/pipes/validation.pipe"; import { plainToInstance, instanceToPlain } from "class-transformer"; import { ValidatorOptions } from "class-validator"; @@ -36,7 +41,10 @@ export class AppValidationPipe extends ValidationPipe { } /** @inheritDoc */ - public override async transform(value: unknown, metadata: ArgumentMetadata) { + public override async transform( + value: unknown, + metadata: ArgumentMetadata + ) { if (metadata.type === "custom" || metadata.type === "param") { return super.transform(value, metadata); } @@ -54,7 +62,10 @@ export class AppValidationPipe extends ValidationPipe { value = jsonify(value); } - return instanceToPlain(await super.transform(value, metadata), transformOptions) as never; + return instanceToPlain( + await super.transform(value, metadata), + transformOptions + ) as never; } /** @inheritDoc */ diff --git a/apps/backend/src/app/_lib/entity/decorators/many-to-one.decorator.ts b/apps/backend/src/app/_lib/entity/decorators/many-to-one.decorator.ts index 266fe6f5..d6e7b18c 100644 --- a/apps/backend/src/app/_lib/entity/decorators/many-to-one.decorator.ts +++ b/apps/backend/src/app/_lib/entity/decorators/many-to-one.decorator.ts @@ -28,7 +28,8 @@ export interface ManyToOneParams { foreign: boolean; } -export type ManyToOnePropertyOptions = ManyToOneFactoryOptions & ManyToOneParams; +export type ManyToOnePropertyOptions = ManyToOneFactoryOptions & + ManyToOneParams; /** * Decorator for relations. It manages two properties for the same fieldName. diff --git a/apps/backend/src/app/_lib/entity/entity-order.converter.spec.ts b/apps/backend/src/app/_lib/entity/entity-order.converter.spec.ts index df410d00..fbb299b2 100644 --- a/apps/backend/src/app/_lib/entity/entity-order.converter.spec.ts +++ b/apps/backend/src/app/_lib/entity/entity-order.converter.spec.ts @@ -51,7 +51,9 @@ describe("entityOrderToQueryOrder", () => { ]; for (const encoded of tests) { - expect(() => entityOrderToQueryOrder(encoded)).toThrow(BadRequestException); + expect(() => entityOrderToQueryOrder(encoded)).toThrow( + BadRequestException + ); } }); }); diff --git a/apps/backend/src/app/_lib/entity/entity-order.converter.ts b/apps/backend/src/app/_lib/entity/entity-order.converter.ts index d86f84e0..b2eac46e 100644 --- a/apps/backend/src/app/_lib/entity/entity-order.converter.ts +++ b/apps/backend/src/app/_lib/entity/entity-order.converter.ts @@ -19,11 +19,15 @@ export type EntityQueryOrder = export type EntityQueryOrderMap = { [K in keyof T as ExcludeFunctions]?: T[K] extends Date | Primitive ? EntityQueryOrder - : EntityQueryOrderMap ? U : T[K]>>; + : EntityQueryOrderMap< + NonNullable ? U : T[K]> + >; }; function entityOrder2QueryOrder(value: string): EntityQueryOrder; -function entityOrder2QueryOrder(order: Readonly>): EntityQueryOrderMap; +function entityOrder2QueryOrder( + order: Readonly> +): EntityQueryOrderMap; /** * Convert an entity order to a Mikro-orm query object compatible entity * @@ -34,7 +38,10 @@ function entityOrder2QueryOrder(order: Readonly>): EntityQuery function entityOrder2QueryOrder( order: ReadonlyDeep> | string ): EntityQueryOrder | EntityQueryOrderMap { - if (typeof order === "number" || [true, false, null, undefined].includes(order as never)) { + if ( + typeof order === "number" || + [true, false, null, undefined].includes(order as never) + ) { throw new BadRequestException(`Unknown order value: ${String(order)}`); } diff --git a/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.spec.ts b/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.spec.ts index 00141c75..90b7d92d 100644 --- a/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.spec.ts +++ b/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.spec.ts @@ -24,10 +24,20 @@ describe("entityToPopulateToRelationsKeys", () => { [{ b: true }, ["b"]], [{ b: {} }, ["b"]], [{ b: true, bs: {} }, ["b", "bs"]], - [{ b: { cs: true }, bs: { cs: true }, cs: { cs: {} } }, ["b.cs", "bs.cs", "cs.cs"]], + [ + { b: { cs: true }, bs: { cs: true }, cs: { cs: {} } }, + ["b.cs", "bs.cs", "cs.cs"] + ], [{ d: { cs: true } }, ["d.cs"]] - ] satisfies Array<[EntitiesToPopulate, Array>]>) { - expect(entityToPopulateToRelationsKeys(toPopulate)).toStrictEqual(keys); + ] satisfies Array< + [ + EntitiesToPopulate, + Array> + ] + >) { + expect(entityToPopulateToRelationsKeys(toPopulate)).toStrictEqual( + keys + ); } }); }); diff --git a/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.ts b/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.ts index d8a42e23..4c99fa2e 100644 --- a/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.ts +++ b/apps/backend/src/app/_lib/entity/entity-to-populate.coverter.ts @@ -12,14 +12,16 @@ import { EntityRelationKeysDeep } from "./entity.types"; export function entityToPopulateToRelationsKeys( toPopulate: EntitiesToPopulate ): Array> { - return Object.entries[keyof EntitiesToPopulate]> | true>( - toPopulate as never - ).flatMap(([key, value]) => { + return Object.entries< + NonNullable[keyof EntitiesToPopulate]> | true + >(toPopulate as never).flatMap(([key, value]) => { if (value === true) { return [key] as never; } const keys: string[] = entityToPopulateToRelationsKeys(value); - return (keys.length ? keys.map(nested => `${key}.${nested}`) : [key]) as never; + return ( + keys.length ? keys.map(nested => `${key}.${nested}`) : [key] + ) as never; }); } diff --git a/apps/backend/src/app/_lib/entity/entity.service.ts b/apps/backend/src/app/_lib/entity/entity.service.ts index 940c50f0..3b21456d 100644 --- a/apps/backend/src/app/_lib/entity/entity.service.ts +++ b/apps/backend/src/app/_lib/entity/entity.service.ts @@ -15,7 +15,10 @@ import { entityToPopulateToRelationsKeys } from "./entity-to-populate.coverter"; /** * Some options when finding entities */ -export interface EntityServiceFindOptions> { +export interface EntityServiceFindOptions< + T extends EntityBase, + P extends EntitiesToPopulate +> { /** * Populate the given relations */ @@ -25,7 +28,10 @@ export interface EntityServiceFindOptions> { +export interface EntityServiceCreateOptions< + T extends EntityBase, + P extends EntitiesToPopulate +> { /** * The options when returning the data */ @@ -93,7 +99,10 @@ export abstract class EntityService< }) .then(([data, total]) => ({ data: data as Array>, - pagination: { range: { end: offset + data.length, start: offset }, total } + pagination: { + range: { end: offset + data.length, start: offset }, + total + } })); } @@ -104,7 +113,9 @@ export abstract class EntityService< * @returns The count of the entities found */ public count(where: EntityFilter = {}) { - return this.findAndCount(where, { limit: 0 }).then(({ pagination: { total } }) => total); + return this.findAndCount(where, { limit: 0 }).then( + ({ pagination: { total } }) => total + ); } /** @@ -121,7 +132,8 @@ export abstract class EntityService< ) { return this.repository.findOneOrFail(where as never, { populate: - options?.populate && (entityToPopulateToRelationsKeys(options.populate) as never) + options?.populate && + (entityToPopulateToRelationsKeys(options.populate) as never) }) as Promise>; } @@ -156,7 +168,9 @@ export abstract class EntityService< ) { // Why as never? Mikro-orm detect optional props by `?` // But it does take account of SQL default value - const created = this.repository.create(toCreate as never, { persist: true }); + const created = this.repository.create(toCreate as never, { + persist: true + }); await this.repository.getEntityManager().flush(); @@ -203,7 +217,9 @@ export abstract class EntityService< * @returns The deleted entity (before deletion) */ public delete(id: EntityId): Promise { - return this.findById(id).then(async entity => this.deleteEntity(entity)); + return this.findById(id).then(async entity => + this.deleteEntity(entity) + ); } /** diff --git a/apps/backend/src/app/_lib/entity/entity.types.ts b/apps/backend/src/app/_lib/entity/entity.types.ts index 88aaaa09..3a4846d7 100644 --- a/apps/backend/src/app/_lib/entity/entity.types.ts +++ b/apps/backend/src/app/_lib/entity/entity.types.ts @@ -22,17 +22,25 @@ type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7]; * * @see EntityRelationKeys */ -export type EntityRelationKeysDeep = +export type EntityRelationKeysDeep< + T extends EntityBase, + D extends Depth[number] = 5 +> = | EntityRelationKeys | (D extends never ? never : { - [K in EntityRelationKeys]: NonNullable extends infer U extends - EntityBase + [K in EntityRelationKeys]: NonNullable< + T[K] + > extends infer U extends EntityBase ? `${K}.${EntityRelationKeysDeep}` - : NonNullable extends Collection + : NonNullable extends Collection< + infer U extends EntityBase + > ? `${K}.${EntityRelationKeysDeep}` - : NonNullable extends Array + : NonNullable extends Array< + infer U extends EntityBase + > ? `${K}.${EntityRelationKeysDeep}` : never; }[EntityRelationKeys]); diff --git a/apps/backend/src/app/_lib/exceptions/bad-logic.exception.ts b/apps/backend/src/app/_lib/exceptions/bad-logic.exception.ts index b32b33b4..97cdde37 100644 --- a/apps/backend/src/app/_lib/exceptions/bad-logic.exception.ts +++ b/apps/backend/src/app/_lib/exceptions/bad-logic.exception.ts @@ -15,6 +15,9 @@ export class BadLogicException extends UnprocessableEntityException { * @param cause that created this exception */ public constructor(description: object, cause?: unknown) { - super("Unprocessable Entity", { cause, description: description as unknown as string }); + super("Unprocessable Entity", { + cause, + description: description as unknown as string + }); } } diff --git a/apps/backend/src/app/auth/auth.controller.ts b/apps/backend/src/app/auth/auth.controller.ts index f1e753e2..5b1e86b6 100644 --- a/apps/backend/src/app/auth/auth.controller.ts +++ b/apps/backend/src/app/auth/auth.controller.ts @@ -4,7 +4,11 @@ import { Response } from "express"; import { CookieOptions } from "express-serve-static-core"; import { AuthLoginDto, AuthRefreshDto } from "~/lib/common/app/auth/dtos"; import { AuthSuccessDto } from "~/lib/common/app/auth/dtos/auth.success.dto"; -import { AUTH_ENDPOINT_PREFIX, AuthEndpoint, AuthEndpoints } from "~/lib/common/app/auth/endpoints"; +import { + AUTH_ENDPOINT_PREFIX, + AuthEndpoint, + AuthEndpoints +} from "~/lib/common/app/auth/endpoints"; import { UserDto } from "~/lib/common/app/user/dtos"; import { authOptions } from "~/lib/common/options"; @@ -80,7 +84,11 @@ export class AuthController implements AuthEndpoint { return this.loginOrRefresh(user!, body, res!); } - private loginOrRefresh(user: UserEntity, body: AuthRefreshDto, res: Response) { + private loginOrRefresh( + user: UserEntity, + body: AuthRefreshDto, + res: Response + ) { return this.service.login(user).then(token => { if (body.cookie) { res.cookie(authOptions.cookies.name, token.access_token, { diff --git a/apps/backend/src/app/auth/auth.guard.ts b/apps/backend/src/app/auth/auth.guard.ts index 65c671ad..ae38410d 100644 --- a/apps/backend/src/app/auth/auth.guard.ts +++ b/apps/backend/src/app/auth/auth.guard.ts @@ -16,5 +16,9 @@ export class AuthGuard extends PassportGuard(STRATEGY_JWT_NAME) {} * @returns The decorator applying Api decorators and guard */ export function UseAuth() { - return applyDecorators(ApiBearerAuth(), ApiCookieAuth(), UseGuards(AuthGuard)); + return applyDecorators( + ApiBearerAuth(), + ApiCookieAuth(), + UseGuards(AuthGuard) + ); } diff --git a/apps/backend/src/app/auth/auth.service.spec.ts b/apps/backend/src/app/auth/auth.service.spec.ts index 36a5c27a..92ad442e 100644 --- a/apps/backend/src/app/auth/auth.service.spec.ts +++ b/apps/backend/src/app/auth/auth.service.spec.ts @@ -42,7 +42,7 @@ describe("AuthService", () => { expect(hash).not.toBe(password); const same = await AuthService.compare(password, hash); - expect(same).toBeTrue(); + expect(same).toBe(true); }); it("should return false when comparing hashes", async () => { @@ -53,7 +53,7 @@ describe("AuthService", () => { expect(clear).not.toBe(password); const same = await AuthService.compare(clear, hash); - expect(same).toBeFalse(); + expect(same).toBe(false); } }); }); @@ -66,10 +66,10 @@ describe("AuthService", () => { const user = await userService.findById(db.users[0]._id); - const now = new Date().getTime(); + const now = Date.now(); const { access_token, expires_at } = await service.login(user); - expect(access_token).toBeString(); + expect(typeof access_token === "string").toBe(true); expect(expires_at).toBeGreaterThanOrEqual(now + timeoutMs - 1500); expect(expires_at).toBeLessThanOrEqual(now + timeoutMs + 1500); }); @@ -103,7 +103,11 @@ describe("AuthService", () => { it("should be ok", async () => { const [{ _id, email }] = db.users; - const user = await service.validateJWT({ _id, email, method: "local" }); + const user = await service.validateJWT({ + _id, + email, + method: "local" + }); expect(user._id).toBe(_id); expect(user.email).toBe(email); }); @@ -121,7 +125,11 @@ describe("AuthService", () => { it("should be not ok with invalid email", async () => { const [{ _id, email }] = db.users; await expect(() => - service.validateJWT({ _id, email: `${email}${email}`, method: "local" }) + service.validateJWT({ + _id, + email: `${email}${email}`, + method: "local" + }) ).rejects.toThrow(UnauthorizedException); }); }); diff --git a/apps/backend/src/app/auth/auth.service.ts b/apps/backend/src/app/auth/auth.service.ts index 866078ae..b96dc641 100644 --- a/apps/backend/src/app/auth/auth.service.ts +++ b/apps/backend/src/app/auth/auth.service.ts @@ -60,7 +60,9 @@ export class AuthService { } satisfies JWTPayload); // To get the real time of the JWT - const { exp } = this.jwtService.decode(access_token) as JWTPayload & { exp: number }; + const { exp } = this.jwtService.decode(access_token) as JWTPayload & { + exp: number; + }; return { access_token, expires_at: exp * 1000, diff --git a/apps/backend/src/app/auth/strategies/jwt.strategy.ts b/apps/backend/src/app/auth/strategies/jwt.strategy.ts index 62a18aa4..9aa44798 100644 --- a/apps/backend/src/app/auth/strategies/jwt.strategy.ts +++ b/apps/backend/src/app/auth/strategies/jwt.strategy.ts @@ -31,9 +31,9 @@ export class JwtStrategy jwtFromRequest: ExtractJwt.fromExtractors([ ExtractJwt.fromAuthHeaderAsBearerToken(), req => - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- In case it's falsy - (req.cookies as Partial>)[authOptions.cookies.name] || - null + (req.cookies as Partial>)[ + authOptions.cookies.name + ] || null ]), secretOrKey: getConfiguration().authentication.secret }); diff --git a/apps/backend/src/app/category/category.controller.spec.ts b/apps/backend/src/app/category/category.controller.spec.ts index 52dae5c5..09f27ebb 100644 --- a/apps/backend/src/app/category/category.controller.spec.ts +++ b/apps/backend/src/app/category/category.controller.spec.ts @@ -10,7 +10,10 @@ describe("CategoryController", () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [CategoryController], - providers: [{ provide: CategoryRepository, useValue: {} }, CategoryService] + providers: [ + { provide: CategoryRepository, useValue: {} }, + CategoryService + ] }).compile(); controller = module.get(CategoryController); diff --git a/apps/backend/src/app/category/category.controller.ts b/apps/backend/src/app/category/category.controller.ts index a7b83ce6..ff581243 100644 --- a/apps/backend/src/app/category/category.controller.ts +++ b/apps/backend/src/app/category/category.controller.ts @@ -1,4 +1,13 @@ -import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common"; +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query +} from "@nestjs/common"; import { ApiCreatedResponse, ApiOkResponse, ApiTags } from "@nestjs/swagger"; import { CategoryCreateDto, @@ -7,7 +16,10 @@ import { CategoryResultsDto, CategoryUpdateDto } from "~/lib/common/app/category/dtos"; -import { CATEGORIES_ENDPOINT_PREFIX, CategoryEndpoint } from "~/lib/common/app/category/endpoints"; +import { + CATEGORIES_ENDPOINT_PREFIX, + CategoryEndpoint +} from "~/lib/common/app/category/endpoints"; import { CategoryEntity } from "./category.entity"; import { CategoryService } from "./category.service"; diff --git a/apps/backend/src/app/category/category.entity.ts b/apps/backend/src/app/category/category.entity.ts index 01804433..4e9f1b40 100644 --- a/apps/backend/src/app/category/category.entity.ts +++ b/apps/backend/src/app/category/category.entity.ts @@ -10,7 +10,10 @@ import { NodeEntity } from "../node/node.entity"; * The entity class to manage categories */ @Entity({ customRepository: () => CategoryRepository }) -export class CategoryEntity extends EntityBase implements DtoToEntity { +export class CategoryEntity + extends EntityBase + implements DtoToEntity +{ /** @inheritDoc */ @Property({ unique: true }) public name!: string; diff --git a/apps/backend/src/app/category/category.service.spec.ts b/apps/backend/src/app/category/category.service.spec.ts index 3ce8acb0..b67858b7 100644 --- a/apps/backend/src/app/category/category.service.spec.ts +++ b/apps/backend/src/app/category/category.service.spec.ts @@ -1,6 +1,12 @@ -import { NotFoundError, UniqueConstraintViolationException } from "@mikro-orm/core"; +import { + NotFoundError, + UniqueConstraintViolationException +} from "@mikro-orm/core"; import { Test, TestingModule } from "@nestjs/testing"; -import { CategoryCreateDto, CategoryUpdateDto } from "~/lib/common/app/category/dtos"; +import { + CategoryCreateDto, + CategoryUpdateDto +} from "~/lib/common/app/category/dtos"; import { CategoryModule } from "./category.module"; import { CategoryService } from "./category.service"; @@ -38,8 +44,11 @@ describe("CategoryService", () => { }); it("should fail when getting one by an unknown id", async () => { - const id = Math.max(...dbTest.db.categories.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + const id = + Math.max(...dbTest.db.categories.map(({ _id }) => _id)) + 1; + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); }); @@ -66,7 +75,9 @@ describe("CategoryService", () => { }); it("should fail when a uniqueness constraint is not respected", async () => { - const toCreate: CategoryCreateDto = { name: dbTest.db.categories[0].name }; + const toCreate: CategoryCreateDto = { + name: dbTest.db.categories[0].name + }; await expect(service.create(toCreate)).rejects.toThrow( UniqueConstraintViolationException ); @@ -82,7 +93,9 @@ describe("CategoryService", () => { // Update an entity and check its content const [category] = dbTest.db.categories; - const toUpdate: CategoryUpdateDto = { name: `${category.name}-${category.name}` }; + const toUpdate: CategoryUpdateDto = { + name: `${category.name}-${category.name}` + }; const updated = await service.update(category._id, toUpdate); expect(updated.name).toBe(toUpdate.name); @@ -107,9 +120,9 @@ describe("CategoryService", () => { const [category1, category2] = dbTest.db.categories; const toUpdate: CategoryUpdateDto = { name: category1.name }; - await expect(service.update(category2._id, toUpdate)).rejects.toThrow( - UniqueConstraintViolationException - ); + await expect( + service.update(category2._id, toUpdate) + ).rejects.toThrow(UniqueConstraintViolationException); }); }); @@ -128,11 +141,14 @@ describe("CategoryService", () => { // Check that the entity is really deleted const { data: after } = await service.findAndCount(); expect(after).toHaveLength(before.length - 1); - expect(after.some(({ _id }) => _id === deleted._id)).toBeFalse(); + expect(after.some(({ _id }) => _id === deleted._id)).toBe( + false + ); }); it("should not delete an unknown id", async () => { - const id = Math.max(...dbTest.db.categories.map(({ _id }) => _id)) + 1; + const id = + Math.max(...dbTest.db.categories.map(({ _id }) => _id)) + 1; await expect(service.delete(id)).rejects.toThrow(NotFoundError); }); }); diff --git a/apps/backend/src/app/category/category.service.ts b/apps/backend/src/app/category/category.service.ts index f6b02b1d..e2296b3b 100644 --- a/apps/backend/src/app/category/category.service.ts +++ b/apps/backend/src/app/category/category.service.ts @@ -1,5 +1,8 @@ import { Injectable } from "@nestjs/common"; -import { CategoryCreateDto, CategoryUpdateDto } from "~/lib/common/app/category/dtos"; +import { + CategoryCreateDto, + CategoryUpdateDto +} from "~/lib/common/app/category/dtos"; import { CategoryEntity } from "./category.entity"; import { CategoryRepository } from "./category.repository"; diff --git a/apps/backend/src/app/graph/arc/graph-arc.controller.ts b/apps/backend/src/app/graph/arc/graph-arc.controller.ts index 6b85ec62..1b74efa4 100644 --- a/apps/backend/src/app/graph/arc/graph-arc.controller.ts +++ b/apps/backend/src/app/graph/arc/graph-arc.controller.ts @@ -10,14 +10,22 @@ import { Query, UseInterceptors } from "@nestjs/common"; -import { ApiCreatedResponse, ApiExcludeEndpoint, ApiOkResponse, ApiTags } from "@nestjs/swagger"; +import { + ApiCreatedResponse, + ApiExcludeEndpoint, + ApiOkResponse, + ApiTags +} from "@nestjs/swagger"; import { GraphArcCreateDto, GraphArcDto, GraphArcQueryDto, GraphArcResultsDto } from "~/lib/common/app/graph/dtos/arc"; -import { generateGraphArcsEndpoint, GraphArcEndpoint } from "~/lib/common/app/graph/endpoints"; +import { + generateGraphArcsEndpoint, + GraphArcEndpoint +} from "~/lib/common/app/graph/endpoints"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { EntityId } from "~/lib/common/dtos/entity"; import { UnshiftParameters } from "~/lib/common/types"; @@ -26,21 +34,32 @@ import { GraphArcEntity } from "./graph-arc.entity"; import { GraphArcService } from "./graph-arc.service"; import { UseAuth } from "../../auth/auth.guard"; import { GraphEntity } from "../graph.entity"; -import { ApiGraphParam, GraphInterceptedParam, GraphInterceptor } from "../graph.interceptor"; +import { + ApiGraphParam, + GraphInterceptedParam, + GraphInterceptor +} from "../graph.interceptor"; /** @internal */ type EndpointBase = GraphArcEndpoint; /** @internal */ type EndpointTransformed = { // Adds a Graph as a first parameter for each function - [K in keyof EndpointBase]: UnshiftParameters; + [K in keyof EndpointBase]: UnshiftParameters< + EndpointBase[K], + [GraphEntity] + >; }; /** * {@link GraphArc} main controller */ @ApiTags("Graph arcs") -@Controller(generateGraphArcsEndpoint(`:${GraphInterceptor.PATH_PARAM}` as unknown as EntityId)) +@Controller( + generateGraphArcsEndpoint( + `:${GraphInterceptor.PATH_PARAM}` as unknown as EntityId + ) +) @UseAuth() @UseInterceptors(GraphInterceptor) export class GraphArcController implements EndpointTransformed { @@ -64,29 +83,43 @@ export class GraphArcController implements EndpointTransformed { @ApiGraphParam() @ApiOkResponse({ type: GraphArcDto }) @Get("/:id") - public findById(@GraphInterceptedParam() graph: GraphEntity, @Param("id") id: number) { + public findById( + @GraphInterceptedParam() graph: GraphEntity, + @Param("id") id: number + ) { return this.validateArcId(graph, id); } @ApiCreatedResponse({ type: GraphArcDto }) @ApiGraphParam() @Post() - public create(@GraphInterceptedParam() _graph: GraphEntity, @Body() body: GraphArcCreateDto) { + public create( + @GraphInterceptedParam() _graph: GraphEntity, + @Body() body: GraphArcCreateDto + ) { // TODO: verify graph return this.service.create(body); } @ApiExcludeEndpoint() @Patch("/:id") - public update(@GraphInterceptedParam() _: GraphEntity, @Param("id") id: number) { + public update( + @GraphInterceptedParam() _: GraphEntity, + @Param("id") id: number + ) { return this.service.update(id, {}); } @ApiGraphParam() @ApiOkResponse({ type: GraphArcDto }) @Delete("/:id") - public delete(@GraphInterceptedParam() graph: GraphEntity, @Param("id") id: number) { - return this.validateArcId(graph, id).then(({ _id }) => this.service.delete(_id)); + public delete( + @GraphInterceptedParam() graph: GraphEntity, + @Param("id") id: number + ) { + return this.validateArcId(graph, id).then(({ _id }) => + this.service.delete(_id) + ); } /** @@ -96,12 +129,20 @@ export class GraphArcController implements EndpointTransformed { * @param id of the arc * @returns the found arc */ - private validateArcId(graph: GraphEntity, id: number): Promise { + private validateArcId( + graph: GraphEntity, + id: number + ): Promise { return this.service - .findById(id, { populate: { from: { node: true }, to: { node: true } } }) + .findById(id, { + populate: { from: { node: true }, to: { node: true } } + }) .then(arc => { for (const kind of [arc.from.node.kind, arc.to.node.kind]) { - if (kind.type !== NodeKindType.VERTEX || kind.__graph !== graph._id) { + if ( + kind.type !== NodeKindType.VERTEX || + kind.__graph !== graph._id + ) { throw new NotFoundException( `No GraphArc{id:${id}} found in Graph{id:${graph._id}}` ); diff --git a/apps/backend/src/app/graph/arc/graph-arc.entity.ts b/apps/backend/src/app/graph/arc/graph-arc.entity.ts index aa820de0..0e38205a 100644 --- a/apps/backend/src/app/graph/arc/graph-arc.entity.ts +++ b/apps/backend/src/app/graph/arc/graph-arc.entity.ts @@ -5,7 +5,10 @@ import { DtoToEntity } from "~/lib/common/dtos/entity/entity.types"; import { GraphArcRepository } from "./graph-arc.repository"; import { EntityBase } from "../../_lib/entity"; -import { ManyToOneFactory, ManyToOneParams } from "../../_lib/entity/decorators"; +import { + ManyToOneFactory, + ManyToOneParams +} from "../../_lib/entity/decorators"; import { NodeInputEntity } from "../../node/input/node-input.entity"; import { NodeOutputEntity } from "../../node/output/node-output.entity"; @@ -44,7 +47,10 @@ function ToProperty(params: Pick) { } @Entity({ customRepository: () => GraphArcRepository }) -export class GraphArcEntity extends EntityBase implements DtoToEntity { +export class GraphArcEntity + extends EntityBase + implements DtoToEntity +{ /** @inheritDoc */ @FromProperty({ foreign: false }) public readonly __from!: number; diff --git a/apps/backend/src/app/graph/arc/graph-arc.service.spec.ts b/apps/backend/src/app/graph/arc/graph-arc.service.spec.ts index 98bfaa85..cef171cd 100644 --- a/apps/backend/src/app/graph/arc/graph-arc.service.spec.ts +++ b/apps/backend/src/app/graph/arc/graph-arc.service.spec.ts @@ -50,7 +50,10 @@ describe("GraphArcService", () => { const variable1 = await nodeService.create( { - behavior: { __node: var1._id, type: NodeBehaviorType.REFERENCE }, + behavior: { + __node: var1._id, + type: NodeBehaviorType.REFERENCE + }, kind: { __graph: graph._id, position: { x: 0, y: 0 }, @@ -62,7 +65,10 @@ describe("GraphArcService", () => { ); const variable2 = await nodeService.create( { - behavior: { __node: var2._id, type: NodeBehaviorType.REFERENCE }, + behavior: { + __node: var2._id, + type: NodeBehaviorType.REFERENCE + }, kind: { __graph: graph._id, position: { x: 0, y: 0 }, @@ -74,7 +80,10 @@ describe("GraphArcService", () => { ); const code = await nodeService.create( { - behavior: { __node: nCode._id, type: NodeBehaviorType.REFERENCE }, + behavior: { + __node: nCode._id, + type: NodeBehaviorType.REFERENCE + }, kind: { __graph: graph._id, position: { x: 0, y: 0 }, @@ -102,13 +111,13 @@ describe("GraphArcService", () => { const __to = Math.max( - ...(db.nodes as MockSeed["graph"]["nodes"]).flatMap(({ inputs }) => - inputs.map(({ _id }) => _id) + ...(db.nodes as MockSeed["graph"]["nodes"]).flatMap( + ({ inputs }) => inputs.map(({ _id }) => _id) ) ) * 2; - await expect(() => service.create({ __from: output._id, __to })).rejects.toThrow( - ForeignKeyConstraintViolationException - ); + await expect(() => + service.create({ __from: output._id, __to }) + ).rejects.toThrow(ForeignKeyConstraintViolationException); }); it("should fail when the output is unknown", async () => { @@ -120,13 +129,13 @@ describe("GraphArcService", () => { const __from = Math.max( - ...(db.nodes as MockSeed["graph"]["nodes"]).flatMap(({ outputs }) => - outputs.map(({ _id }) => _id) + ...(db.nodes as MockSeed["graph"]["nodes"]).flatMap( + ({ outputs }) => outputs.map(({ _id }) => _id) ) ) * 2; - await expect(() => service.create({ __from, __to: input._id })).rejects.toThrow( - ForeignKeyConstraintViolationException - ); + await expect(() => + service.create({ __from, __to: input._id }) + ).rejects.toThrow(ForeignKeyConstraintViolationException); }); it("should fail when the input is already used", async () => { @@ -243,7 +252,9 @@ describe("GraphArcService", () => { it("should fail when getting one by an unknown id", async () => { const id = Math.max(...graphArcs.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); }); @@ -272,17 +283,17 @@ describe("GraphArcService", () => { it("should fail when a uniqueness constraint is not respected", async () => { const [{ __to }] = graphArcs; - await expect(() => service.create({ __from: 123123, __to })).rejects.toThrow( - UniqueConstraintViolationException - ); + await expect(() => + service.create({ __from: 123123, __to }) + ).rejects.toThrow(UniqueConstraintViolationException); }); }); describe("Update", () => { it("should fail when updated a graph-arc", async () => { - await expect(() => service.update(graphArcs[0]._id, {})).rejects.toThrow( - MethodNotAllowedException - ); + await expect(() => + service.update(graphArcs[0]._id, {}) + ).rejects.toThrow(MethodNotAllowedException); }); }); diff --git a/apps/backend/src/app/graph/arc/graph-arc.service.ts b/apps/backend/src/app/graph/arc/graph-arc.service.ts index 4c1c974c..b2a853ad 100644 --- a/apps/backend/src/app/graph/arc/graph-arc.service.ts +++ b/apps/backend/src/app/graph/arc/graph-arc.service.ts @@ -12,7 +12,11 @@ import { GraphArcCreateDto } from "~/lib/common/app/graph/dtos/arc"; import { getAdjacencyList } from "~/lib/common/app/graph/transformations"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { EntityId } from "~/lib/common/dtos/entity"; -import { EntitiesToPopulate, EntityFilter, EntityFindParams } from "~/lib/common/endpoints"; +import { + EntitiesToPopulate, + EntityFilter, + EntityFindParams +} from "~/lib/common/endpoints"; import { GraphArcDifferentGraphException } from "./exceptions"; import { GraphArcEntity } from "./graph-arc.entity"; @@ -26,7 +30,11 @@ import { GraphCyclicException } from "../exceptions"; */ @Injectable() export class GraphArcService - extends EntityService> + extends EntityService< + GraphArcEntity, + GraphArcCreateDto, + Record + > implements EventSubscriber { /** @@ -37,11 +45,15 @@ export class GraphArcService */ public constructor( repository: GraphArcRepository, - @Inject(forwardRef(() => NodeService)) private readonly nodeService: NodeService + @Inject(forwardRef(() => NodeService)) + private readonly nodeService: NodeService ) { super(repository); - repository.getEntityManager().getEventManager().registerSubscriber(this); + repository + .getEntityManager() + .getEventManager() + .registerSubscriber(this); } /** @inheritDoc */ @@ -58,11 +70,17 @@ export class GraphArcService const { data: [nodeA], pagination: { total: totalA } - } = await this.nodeService.findAndCount({ inputs: { _id: __to } }, { limit: 1 }); + } = await this.nodeService.findAndCount( + { inputs: { _id: __to } }, + { limit: 1 } + ); const { data: [nodeB], pagination: { total: totalB } - } = await this.nodeService.findAndCount({ outputs: { _id: __from } }, { limit: 1 }); + } = await this.nodeService.findAndCount( + { outputs: { _id: __from } }, + { limit: 1 } + ); if (totalA + totalB <= 1) { // Let the FK error be triggered @@ -72,7 +90,10 @@ export class GraphArcService const { kind: nodeAKind } = nodeA; const { kind: nodeBKind } = nodeB; - if (nodeAKind.type !== NodeKindType.VERTEX || nodeBKind.type !== NodeKindType.VERTEX) { + if ( + nodeAKind.type !== NodeKindType.VERTEX || + nodeBKind.type !== NodeKindType.VERTEX + ) { throw new NotImplementedException(); } @@ -115,8 +136,26 @@ export class GraphArcService { $and: [where], $or: [ - { from: { node: { kind: { __graph: graphId, type: NodeKindType.VERTEX } } } }, - { to: { node: { kind: { __graph: graphId, type: NodeKindType.VERTEX } } } } + { + from: { + node: { + kind: { + __graph: graphId, + type: NodeKindType.VERTEX + } + } + } + }, + { + to: { + node: { + kind: { + __graph: graphId, + type: NodeKindType.VERTEX + } + } + } + } ] }, params, @@ -132,6 +171,8 @@ export class GraphArcService * @returns a Promise that rejects */ public override update(_id: EntityId, _toUpdate: Record) { - return Promise.reject(new MethodNotAllowedException("An arc can not be updated")); + return Promise.reject( + new MethodNotAllowedException("An arc can not be updated") + ); } } diff --git a/apps/backend/src/app/graph/executor/graph.executor.spec.ts b/apps/backend/src/app/graph/executor/graph.executor.spec.ts index 41ccb1a6..363161dd 100644 --- a/apps/backend/src/app/graph/executor/graph.executor.spec.ts +++ b/apps/backend/src/app/graph/executor/graph.executor.spec.ts @@ -52,7 +52,10 @@ describe("GraphExecutor", () => { [node.inputs[1]._id, 3] ]), startAt: nodes - .filter(({ behavior: { type } }) => type === NodeBehaviorType.PARAMETER_IN) + .filter( + ({ behavior: { type } }) => + type === NodeBehaviorType.PARAMETER_IN + ) .map(({ _id }) => _id) }); @@ -61,7 +64,9 @@ describe("GraphExecutor", () => { expect(states).toHaveLength(nodes.length * 4); for (const { _id } of nodes) { // Each node is called 4 times (resolution + propagation) - expect(states.filter(({ node }) => node._id === _id)).toHaveLength(4); + expect(states.filter(({ node }) => node._id === _id)).toHaveLength( + 4 + ); } }); @@ -77,7 +82,11 @@ describe("GraphExecutor", () => { const nodeUnconnected = await nodeService.create({ behavior: { type: NodeBehaviorType.VARIABLE, value: 0 }, - kind: { __graph: graph._id, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + kind: { + __graph: graph._id, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "unconnected" }); @@ -91,7 +100,9 @@ describe("GraphExecutor", () => { }); const states = await lastValueFrom(state$.pipe(toArray())); - expect(states.some(({ node }) => node._id === nodeUnconnected._id)).toBeFalse(); + expect( + states.some(({ node }) => node._id === nodeUnconnected._id) + ).toBe(false); }); describe("Errors", () => { diff --git a/apps/backend/src/app/graph/executor/graph.executor.state.ts b/apps/backend/src/app/graph/executor/graph.executor.state.ts index bc3793e3..e51cf7b6 100644 --- a/apps/backend/src/app/graph/executor/graph.executor.state.ts +++ b/apps/backend/src/app/graph/executor/graph.executor.state.ts @@ -15,7 +15,8 @@ interface GraphExecuteStateDiscriminated { /** * When a node starts its resolution */ -export type GraphExecuteResolutionStartState = GraphExecuteStateDiscriminated<"resolution-start">; +export type GraphExecuteResolutionStartState = + GraphExecuteStateDiscriminated<"resolution-start">; /** * When a node ends its resolution (after being executed) @@ -32,12 +33,14 @@ export interface GraphExecuteResolutionEndState * When a node enters the flow propagation process. * After it has been resolved, before propagation */ -export type GraphExecutePropagationEnterState = GraphExecuteStateDiscriminated<"propagation-enter">; +export type GraphExecutePropagationEnterState = + GraphExecuteStateDiscriminated<"propagation-enter">; /** * When a node leave the flow propagation process. * After it has been resolved, after propagation */ -export type GraphExecutePropagationLeaveState = GraphExecuteStateDiscriminated<"propagation-leave">; +export type GraphExecutePropagationLeaveState = + GraphExecuteStateDiscriminated<"propagation-leave">; export type GraphExecuteState = | GraphExecutePropagationEnterState diff --git a/apps/backend/src/app/graph/executor/graph.executor.ts b/apps/backend/src/app/graph/executor/graph.executor.ts index ca9a6999..f9974c5b 100644 --- a/apps/backend/src/app/graph/executor/graph.executor.ts +++ b/apps/backend/src/app/graph/executor/graph.executor.ts @@ -7,7 +7,10 @@ import { EntityId } from "~/lib/common/dtos/entity"; import { GraphExecutorStartingNodeException } from "./exceptions"; import { GraphExecuteState } from "./graph.executor.state"; import { GraphResolver, GraphResolverNode } from "./graph.resolver"; -import { NodeExecutor, NodeInputAndValues } from "../../node/executor/node.executor"; +import { + NodeExecutor, + NodeInputAndValues +} from "../../node/executor/node.executor"; import { NodeService } from "../../node/node.service"; import { GraphArcService } from "../arc/graph-arc.service"; @@ -73,12 +76,14 @@ export class GraphExecutor { const { graphId, inputs, startAt } = params; const { data: arcs } = await this.arcService.findByGraph(graphId); - const nodes = await this.nodeService.findByGraph(graphId).then(({ data }) => - data.map(node => ({ - ...(node.toJSON() as NodeDto), - entity: node - })) - ); + const nodes = await this.nodeService + .findByGraph(graphId) + .then(({ data }) => + data.map(node => ({ + ...(node.toJSON() as NodeDto), + entity: node + })) + ); const roots = nodes.filter(({ _id }) => startAt.includes(_id)); if (startAt.length === 0 || roots.length !== startAt.length) { diff --git a/apps/backend/src/app/graph/executor/graph.resolver.ts b/apps/backend/src/app/graph/executor/graph.resolver.ts index 5488b285..b78cd1c5 100644 --- a/apps/backend/src/app/graph/executor/graph.resolver.ts +++ b/apps/backend/src/app/graph/executor/graph.resolver.ts @@ -1,6 +1,15 @@ -import { lastValueFrom, Observable, ReplaySubject, Subscription, takeWhile } from "rxjs"; +import { + lastValueFrom, + Observable, + ReplaySubject, + Subscription, + takeWhile +} from "rxjs"; import { match, P } from "ts-pattern"; -import { AdjacencyListArc, AdjacencyListItem } from "~/lib/common/app/graph/transformations"; +import { + AdjacencyListArc, + AdjacencyListItem +} from "~/lib/common/app/graph/transformations"; import { NodeDto } from "~/lib/common/app/node/dtos"; import { NodeIoValue } from "~/lib/common/app/node/io"; import { EntityId } from "~/lib/common/dtos/entity"; @@ -25,7 +34,10 @@ export interface GraphResolverNode extends NodeDto { * The initials nodes for the resolver. * They are supposed to come from an AdjacencyList Object. */ -export type GraphResolverRoot = AdjacencyListItem; +export type GraphResolverRoot = AdjacencyListItem< + AdjacencyListArc, + GraphResolverNode +>; /** * Internal for the {@link GraphExecutor}. @@ -68,9 +80,16 @@ export class GraphResolver { this.subscription = this.state$.subscribe(state => match(state) - .with({ type: "resolution-start" }, ({ node }) => this.resolved.add(node)) - .with({ type: "propagation-enter" }, ({ node }) => this.propagated.add(node)) - .with({ type: P.union("propagation-leave", "resolution-end") }, () => void 0) + .with({ type: "resolution-start" }, ({ node }) => + this.resolved.add(node) + ) + .with({ type: "propagation-enter" }, ({ node }) => + this.propagated.add(node) + ) + .with( + { type: P.union("propagation-leave", "resolution-end") }, + () => void 0 + ) .exhaustive() ); } @@ -155,7 +174,8 @@ export class GraphResolver { return lastValueFrom( this.state$.pipe( takeWhile( - ({ node: { _id }, type }) => !(type === "resolution-end" && _id === node._id) + ({ node: { _id }, type }) => + !(type === "resolution-end" && _id === node._id) ) ) ).then(); diff --git a/apps/backend/src/app/graph/graph.controller.spec.ts b/apps/backend/src/app/graph/graph.controller.spec.ts index 6495bc90..884c506d 100644 --- a/apps/backend/src/app/graph/graph.controller.spec.ts +++ b/apps/backend/src/app/graph/graph.controller.spec.ts @@ -10,7 +10,10 @@ describe("GraphController", () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [GraphController], - providers: [{ provide: GraphRepository, useValue: {} }, GraphService] + providers: [ + { provide: GraphRepository, useValue: {} }, + GraphService + ] }).compile(); controller = module.get(GraphController); diff --git a/apps/backend/src/app/graph/graph.controller.ts b/apps/backend/src/app/graph/graph.controller.ts index 368d4a1a..d1746824 100644 --- a/apps/backend/src/app/graph/graph.controller.ts +++ b/apps/backend/src/app/graph/graph.controller.ts @@ -1,6 +1,10 @@ import { Controller, Delete, Get, Param, Post, Query } from "@nestjs/common"; import { ApiExcludeEndpoint, ApiOkResponse, ApiTags } from "@nestjs/swagger"; -import { GraphDto, GraphQueryDto, GraphResultsDto } from "~/lib/common/app/graph/dtos"; +import { + GraphDto, + GraphQueryDto, + GraphResultsDto +} from "~/lib/common/app/graph/dtos"; import { GraphEndpoint, GRAPHS_ENDPOINT_PREFIX diff --git a/apps/backend/src/app/graph/graph.entity.ts b/apps/backend/src/app/graph/graph.entity.ts index af615669..64aee9b3 100644 --- a/apps/backend/src/app/graph/graph.entity.ts +++ b/apps/backend/src/app/graph/graph.entity.ts @@ -18,9 +18,15 @@ export class GraphEntity extends EntityBase implements DtoToEntity { * The reverse relation of a NodeBehavior. * Currently only for `node-function`s */ - @OneToOne(() => NodeBehaviorFunction, ({ graph }) => graph, { hidden: true, owner: false }) + @OneToOne(() => NodeBehaviorFunction, ({ graph }) => graph, { + hidden: true, + owner: false + }) public readonly nodeBehavior?: NodeBehaviorFunction | null; - @OneToOne(() => WorkflowEntity, ({ graph }) => graph, { hidden: true, owner: false }) + @OneToOne(() => WorkflowEntity, ({ graph }) => graph, { + hidden: true, + owner: false + }) public readonly workflow?: WorkflowEntity | null; } diff --git a/apps/backend/src/app/graph/graph.interceptor.ts b/apps/backend/src/app/graph/graph.interceptor.ts index 689f3a2b..369431bf 100644 --- a/apps/backend/src/app/graph/graph.interceptor.ts +++ b/apps/backend/src/app/graph/graph.interceptor.ts @@ -14,9 +14,9 @@ import { Request } from "express"; import { GraphEntity } from "./graph.entity"; import { GraphService } from "./graph.service"; -// eslint-disable-next-line no-use-before-define -- Only for typing -type RequestParams = Partial> & - // eslint-disable-next-line no-use-before-define -- Only for typing +type RequestParams = Partial< + Record +> & Record; /** @@ -47,7 +47,9 @@ export class GraphInterceptor implements NestInterceptor { /** @inheritDoc */ public intercept(context: ExecutionContext, next: CallHandler) { - const request = context.switchToHttp().getRequest>(); + const request = context + .switchToHttp() + .getRequest>(); const graphId = request.params[GraphInterceptor.PATH_PARAM]; const id = +graphId; @@ -67,7 +69,10 @@ export class GraphInterceptor implements NestInterceptor { /** * Injects the intercepted Graph from the {@link GraphInterceptor} into a parameter. */ -export const GraphInterceptedParam = createParamDecorator( +export const GraphInterceptedParam = createParamDecorator< + never, + ExecutionContext +>( (_, context) => context.switchToHttp().getRequest>().params[ GraphInterceptor.GRAPH_TOKEN @@ -81,7 +86,9 @@ export const GraphInterceptedParam = createParamDecorator = {}) => +export const ApiGraphParam = ( + options: Pick = {} +) => ApiParam({ ...options, name: GraphInterceptor.PATH_PARAM, diff --git a/apps/backend/src/app/graph/graph.module.ts b/apps/backend/src/app/graph/graph.module.ts index 31d38384..9ad402d1 100644 --- a/apps/backend/src/app/graph/graph.module.ts +++ b/apps/backend/src/app/graph/graph.module.ts @@ -16,7 +16,10 @@ import { NodeModule } from "../node/node.module"; @Module({ controllers: [GraphArcController, GraphController, GraphNodeController], exports: [GraphExecutor, GraphService], - imports: [forwardRef(() => NodeModule), MikroOrmModule.forFeature(GRAPH_ENTITIES)], + imports: [ + forwardRef(() => NodeModule), + MikroOrmModule.forFeature(GRAPH_ENTITIES) + ], providers: [GraphArcService, GraphExecutor, GraphService] }) export class GraphModule {} diff --git a/apps/backend/src/app/graph/graph.service.spec.ts b/apps/backend/src/app/graph/graph.service.spec.ts index 57326742..712bcf45 100644 --- a/apps/backend/src/app/graph/graph.service.spec.ts +++ b/apps/backend/src/app/graph/graph.service.spec.ts @@ -45,21 +45,25 @@ describe("GraphService", () => { it("should fail when getting one by an unknown id", async () => { const id = Math.max(...graphs.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); }); describe("Create", () => { it("should fail when creating a graph directly", async () => { - await expect(() => service.create({})).rejects.toThrow(MethodNotAllowedException); + await expect(() => service.create({})).rejects.toThrow( + MethodNotAllowedException + ); }); }); describe("Delete", () => { it("should fail when deleting a graph directly", async () => { - await expect(() => service.delete(graphs[0]._id)).rejects.toThrow( - MethodNotAllowedException - ); + await expect(() => + service.delete(graphs[0]._id) + ).rejects.toThrow(MethodNotAllowedException); }); }); }); diff --git a/apps/backend/src/app/graph/graph.service.ts b/apps/backend/src/app/graph/graph.service.ts index 7f999aef..2fc577fc 100644 --- a/apps/backend/src/app/graph/graph.service.ts +++ b/apps/backend/src/app/graph/graph.service.ts @@ -9,7 +9,11 @@ import { EntityService } from "../_lib/entity"; * Service to manages [graphs]{@link GraphEntity}. */ @Injectable() -export class GraphService extends EntityService, unknown> { +export class GraphService extends EntityService< + GraphEntity, + Record, + unknown +> { // TODO: update types /** diff --git a/apps/backend/src/app/graph/node/graph-node.controller.ts b/apps/backend/src/app/graph/node/graph-node.controller.ts index b433afbb..8795ebaf 100644 --- a/apps/backend/src/app/graph/node/graph-node.controller.ts +++ b/apps/backend/src/app/graph/node/graph-node.controller.ts @@ -15,7 +15,10 @@ import { import { ApiCreatedResponse, ApiOkResponse, ApiTags } from "@nestjs/swagger"; import { GraphNodeUpdateDto } from "~/lib/common/app/graph/dtos/node"; import { GraphNodeCreateDto } from "~/lib/common/app/graph/dtos/node/graph-node.create.dto"; -import { generateGraphNodesEndpoint, GraphNodeEndpoint } from "~/lib/common/app/graph/endpoints"; +import { + generateGraphNodesEndpoint, + GraphNodeEndpoint +} from "~/lib/common/app/graph/endpoints"; import { NodeDto, NodeQueryDto } from "~/lib/common/app/node/dtos"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { EntityId } from "~/lib/common/dtos/entity"; @@ -25,20 +28,31 @@ import { UseAuth } from "../../auth/auth.guard"; import { NodeEntity } from "../../node/node.entity"; import { NodeService } from "../../node/node.service"; import { GraphEntity } from "../graph.entity"; -import { ApiGraphParam, GraphInterceptedParam, GraphInterceptor } from "../graph.interceptor"; +import { + ApiGraphParam, + GraphInterceptedParam, + GraphInterceptor +} from "../graph.interceptor"; -// @ts-expect-error - TS2344: Narrowed by the condition +// @ts-expect-error -- TS2344: Narrowed by the condition type EndpointBase = GraphNodeEndpoint; type EndpointTransformed = { // Adds a Graph as a first parameter for each function - [K in keyof EndpointBase]: UnshiftParameters; + [K in keyof EndpointBase]: UnshiftParameters< + EndpointBase[K], + [GraphEntity] + >; }; /** * {@link NodeEntity} from a {@link GraphEntity} controller */ @ApiTags("Graph nodes") -@Controller(generateGraphNodesEndpoint(`:${GraphInterceptor.PATH_PARAM}` as unknown as EntityId)) +@Controller( + generateGraphNodesEndpoint( + `:${GraphInterceptor.PATH_PARAM}` as unknown as EntityId + ) +) @UseAuth() @UseInterceptors(GraphInterceptor) export class GraphNodeController implements EndpointTransformed { @@ -48,7 +62,8 @@ export class GraphNodeController implements EndpointTransformed { * @param service injected */ public constructor( - @Inject(forwardRef(() => NodeService)) private readonly service: NodeService + @Inject(forwardRef(() => NodeService)) + private readonly service: NodeService ) {} @ApiGraphParam() @@ -64,17 +79,27 @@ export class GraphNodeController implements EndpointTransformed { @ApiGraphParam() @ApiOkResponse({ type: NodeDto }) @Get("/:id") - public findById(@GraphInterceptedParam() graph: GraphEntity, @Param("id") id: number) { + public findById( + @GraphInterceptedParam() graph: GraphEntity, + @Param("id") id: number + ) { return this.validateNodeId(graph, id); } @ApiCreatedResponse({ type: NodeDto }) @ApiGraphParam() @Post() - public create(@GraphInterceptedParam() graph: GraphEntity, @Body() body: GraphNodeCreateDto) { + public create( + @GraphInterceptedParam() graph: GraphEntity, + @Body() body: GraphNodeCreateDto + ) { return this.service.create({ ...body, - kind: { __graph: graph._id, position: body.kind.position, type: NodeKindType.VERTEX } + kind: { + __graph: graph._id, + position: body.kind.position, + type: NodeKindType.VERTEX + } }); } @@ -97,16 +122,26 @@ export class GraphNodeController implements EndpointTransformed { @ApiGraphParam() @ApiOkResponse({ type: NodeDto }) @Delete("/:id") - public delete(@GraphInterceptedParam() graph: GraphEntity, @Param("id") id: number) { - return this.validateNodeId(graph, id).then(({ _id }) => this.service.delete(_id)); + public delete( + @GraphInterceptedParam() graph: GraphEntity, + @Param("id") id: number + ) { + return this.validateNodeId(graph, id).then(({ _id }) => + this.service.delete(_id) + ); } private validateNodeId(graph: GraphEntity, id: number) { // Determine if the graph contains the node that is being manipulated return this.service.findById(id).then(node => { const { kind } = node; - if (kind.type !== NodeKindType.VERTEX || kind.__graph !== graph._id) { - throw new NotFoundException(`No Node{id:${id}} found in graph{id:${graph._id}}`); + if ( + kind.type !== NodeKindType.VERTEX || + kind.__graph !== graph._id + ) { + throw new NotFoundException( + `No Node{id:${id}} found in graph{id:${graph._id}}` + ); } return node; diff --git a/apps/backend/src/app/node/behaviors/node-behavior.base.ts b/apps/backend/src/app/node/behaviors/node-behavior.base.ts index 0e7c1a65..4311ae53 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.base.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.base.ts @@ -13,15 +13,15 @@ import { NodeEntity } from "../node.entity"; export const NODE_BEHAVIOR_ENTITY_OPTIONS = { discriminatorColumn: "type" satisfies NodeBehaviorDiscriminatorKey, tableName: "node_behavior" - // eslint-disable-next-line no-use-before-define -- Defined just after } as const satisfies EntityOptions; @Entity({ ...NODE_BEHAVIOR_ENTITY_OPTIONS, abstract: true }) -export abstract class NodeBehaviorBase - implements NodeBehaviorBaseDto +export abstract class NodeBehaviorBase< + Type extends NodeBehaviorType = NodeBehaviorType +> implements NodeBehaviorBaseDto { /** @inheritDoc */ @Enum({ items: () => NodeBehaviorType, type: () => NodeBehaviorType }) diff --git a/apps/backend/src/app/node/behaviors/node-behavior.code.ts b/apps/backend/src/app/node/behaviors/node-behavior.code.ts index 1e6c2b2d..b276b183 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.code.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.code.ts @@ -7,7 +7,10 @@ import { NodeBehaviorBase } from "./node-behavior.base"; const type = NodeBehaviorType.CODE; @Entity({ discriminatorValue: type }) -export class NodeBehaviorCode extends NodeBehaviorBase implements DTO { +export class NodeBehaviorCode + extends NodeBehaviorBase + implements DTO +{ @Property({ type: types.text }) public code!: string; } diff --git a/apps/backend/src/app/node/behaviors/node-behavior.entity.ts b/apps/backend/src/app/node/behaviors/node-behavior.entity.ts index efe57e7b..04f9ed71 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.entity.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.entity.ts @@ -25,4 +25,6 @@ export const NODE_BEHAVIOR_ENTITIES = [ /** * The union type of all node behaviors */ -export type NodeBehaviorEntity = InstanceType<(typeof NODE_BEHAVIOR_ENTITIES)[number]>; +export type NodeBehaviorEntity = InstanceType< + (typeof NODE_BEHAVIOR_ENTITIES)[number] +>; diff --git a/apps/backend/src/app/node/behaviors/node-behavior.function.ts b/apps/backend/src/app/node/behaviors/node-behavior.function.ts index 2aa622ad..9cc44ca6 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.function.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.function.ts @@ -10,9 +10,16 @@ const fieldName = "__graph" satisfies keyof DTO; const type = NodeBehaviorType.FUNCTION; @Entity({ discriminatorValue: type }) -export class NodeBehaviorFunction extends NodeBehaviorBase implements DTO { +export class NodeBehaviorFunction + extends NodeBehaviorBase + implements DTO +{ /** @inheritDoc */ - @Property({ fieldName, formula: alias => `${alias}.${fieldName}`, persist: false }) + @Property({ + fieldName, + formula: alias => `${alias}.${fieldName}`, + persist: false + }) public readonly __graph!: number; @OneToOne(() => GraphEntity, { diff --git a/apps/backend/src/app/node/behaviors/node-behavior.parameter-input.ts b/apps/backend/src/app/node/behaviors/node-behavior.parameter-input.ts index 23f7114b..cf06167c 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.parameter-input.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.parameter-input.ts @@ -13,9 +13,16 @@ const type = NodeBehaviorType.PARAMETER_IN; * Node behavior of `PARAMETER_IN` type */ @Entity({ discriminatorValue: type }) -export class NodeBehaviorParameterInput extends NodeBehaviorBase implements DTO { +export class NodeBehaviorParameterInput + extends NodeBehaviorBase + implements DTO +{ /** @inheritDoc */ - @Property({ fieldName, formula: alias => `${alias}.${fieldName}`, persist: false }) + @Property({ + fieldName, + formula: alias => `${alias}.${fieldName}`, + persist: false + }) public readonly __node_input!: number; /** @inheritDoc */ diff --git a/apps/backend/src/app/node/behaviors/node-behavior.parameter-output.ts b/apps/backend/src/app/node/behaviors/node-behavior.parameter-output.ts index 33a642b0..be1e258b 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.parameter-output.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.parameter-output.ts @@ -13,9 +13,16 @@ const type = NodeBehaviorType.PARAMETER_OUT; * Node behavior of `PARAMETER_OUT` type */ @Entity({ discriminatorValue: type }) -export class NodeBehaviorParameterOutput extends NodeBehaviorBase implements DTO { +export class NodeBehaviorParameterOutput + extends NodeBehaviorBase + implements DTO +{ /** @inheritDoc */ - @Property({ fieldName, formula: alias => `${alias}.${fieldName}`, persist: false }) + @Property({ + fieldName, + formula: alias => `${alias}.${fieldName}`, + persist: false + }) public readonly __node_output!: number; /** @inheritDoc */ diff --git a/apps/backend/src/app/node/behaviors/node-behavior.reference.ts b/apps/backend/src/app/node/behaviors/node-behavior.reference.ts index 76f7a98c..4b909198 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.reference.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.reference.ts @@ -16,7 +16,10 @@ const NodeProperty = ManyToOneFactory(() => NodeEntity, { }); @Entity({ discriminatorValue: type }) -export class NodeBehaviorReference extends NodeBehaviorBase implements DTO { +export class NodeBehaviorReference + extends NodeBehaviorBase + implements DTO +{ /** @inheritDoc */ @NodeProperty({ foreign: false }) public readonly __node!: EntityId; diff --git a/apps/backend/src/app/node/behaviors/node-behavior.trigger.ts b/apps/backend/src/app/node/behaviors/node-behavior.trigger.ts index 89612737..41422480 100644 --- a/apps/backend/src/app/node/behaviors/node-behavior.trigger.ts +++ b/apps/backend/src/app/node/behaviors/node-behavior.trigger.ts @@ -8,7 +8,10 @@ import { NODE_TRIGGER_ENTITIES, NodeTriggerEntity } from "./triggers"; const type = NodeBehaviorType.TRIGGER; @Entity({ discriminatorValue: type }) -export class NodeBehaviorTrigger extends NodeBehaviorBase implements DTO { +export class NodeBehaviorTrigger + extends NodeBehaviorBase + implements DTO +{ /** @inheritDoc */ @Embedded(() => NODE_TRIGGER_ENTITIES, { object: true }) public readonly trigger!: NodeTriggerEntity; diff --git a/apps/backend/src/app/node/behaviors/triggers/node.trigger.entity.ts b/apps/backend/src/app/node/behaviors/triggers/node.trigger.entity.ts index a6206d7b..43a3e4a4 100644 --- a/apps/backend/src/app/node/behaviors/triggers/node.trigger.entity.ts +++ b/apps/backend/src/app/node/behaviors/triggers/node.trigger.entity.ts @@ -6,11 +6,13 @@ import { NodeTriggerCron } from "./node.trigger.cron"; /** * All the sub-entities for a `node-trigger` */ -export const NODE_TRIGGER_ENTITIES = [NodeTriggerCron] as const satisfies ReadonlyArray< - Type ->; +export const NODE_TRIGGER_ENTITIES = [ + NodeTriggerCron +] as const satisfies ReadonlyArray>; /** * The union type of all node trigger */ -export type NodeTriggerEntity = InstanceType<(typeof NODE_TRIGGER_ENTITIES)[number]>; +export type NodeTriggerEntity = InstanceType< + (typeof NODE_TRIGGER_ENTITIES)[number] +>; diff --git a/apps/backend/src/app/node/exceptions/node.no-template-parameter.exception.ts b/apps/backend/src/app/node/exceptions/node.no-template-parameter.exception.ts index 2b09d7c9..25572f67 100644 --- a/apps/backend/src/app/node/exceptions/node.no-template-parameter.exception.ts +++ b/apps/backend/src/app/node/exceptions/node.no-template-parameter.exception.ts @@ -10,6 +10,9 @@ export class NodeNoTemplateParameterException extends NodeException { * Creates the exception */ public constructor() { - super(NodeErrorCode.NO_TEMPLATE_PARAMETER, "Parameter nodes can not be templates"); + super( + NodeErrorCode.NO_TEMPLATE_PARAMETER, + "Parameter nodes can not be templates" + ); } } diff --git a/apps/backend/src/app/node/exceptions/node.readonly-kind-type.exception.ts b/apps/backend/src/app/node/exceptions/node.readonly-kind-type.exception.ts index 6617c762..96adebac 100644 --- a/apps/backend/src/app/node/exceptions/node.readonly-kind-type.exception.ts +++ b/apps/backend/src/app/node/exceptions/node.readonly-kind-type.exception.ts @@ -10,6 +10,9 @@ export class NodeReadonlyKindTypeException extends NodeException { * Creates the exception */ public constructor() { - super(NodeErrorCode.KIND_TYPE_READONLY, "The kind-type of a node can not be changed"); + super( + NodeErrorCode.KIND_TYPE_READONLY, + "The kind-type of a node can not be changed" + ); } } diff --git a/apps/backend/src/app/node/executor/exceptions/node-executor.missing-input.exception.ts b/apps/backend/src/app/node/executor/exceptions/node-executor.missing-input.exception.ts index 2e025c88..2e6ace48 100644 --- a/apps/backend/src/app/node/executor/exceptions/node-executor.missing-input.exception.ts +++ b/apps/backend/src/app/node/executor/exceptions/node-executor.missing-input.exception.ts @@ -13,6 +13,9 @@ export class NodeExecutorMissingInputException extends NodeExecutorException { * @param inputId The input that has no value */ public constructor(inputId: EntityId) { - super(NodeExecutorErrorCode.MISSING_INPUT, `The ${inputId} has no value.`); + super( + NodeExecutorErrorCode.MISSING_INPUT, + `The ${inputId} has no value.` + ); } } diff --git a/apps/backend/src/app/node/executor/node.executor.spec.ts b/apps/backend/src/app/node/executor/node.executor.spec.ts index f2c1f061..6f50e479 100644 --- a/apps/backend/src/app/node/executor/node.executor.spec.ts +++ b/apps/backend/src/app/node/executor/node.executor.spec.ts @@ -47,9 +47,9 @@ describe("NodeExecutor", () => { // Division const node = await service.findById(db.graph.nodes[5]._id); expect(node.behavior.type).toBe(NodeBehaviorType.CODE); - await expect(() => executor.execute({ node, valuedInputs: new Map() })).rejects.toThrow( - NodeExecutorMissingInputException - ); + await expect(() => + executor.execute({ node, valuedInputs: new Map() }) + ).rejects.toThrow(NodeExecutorMissingInputException); }); }); @@ -97,12 +97,18 @@ describe("NodeExecutor", () => { expect(quotientOutputs).toHaveLength(1); expect(remainderOutputs).toHaveLength(1); - const [{ output: quotientOutput, value: quotient }] = quotientOutputs; - const [{ output: remainderOutput, value: remainder }] = remainderOutputs; + const [{ output: quotientOutput, value: quotient }] = + quotientOutputs; + const [{ output: remainderOutput, value: remainder }] = + remainderOutputs; // The values are correct - expect(quotient).toBe(divisor === 0 ? 0 : Math.floor(dividend / divisor)); - expect(remainder).toBe(divisor === 0 ? dividend : dividend % divisor); + expect(quotient).toBe( + divisor === 0 ? 0 : Math.floor(dividend / divisor) + ); + expect(remainder).toBe( + divisor === 0 ? dividend : dividend % divisor + ); // The outputs of the nodes are correct expect(quotientOutput).toStrictEqual(codeQuotientOutput); @@ -143,8 +149,12 @@ describe("NodeExecutor", () => { )!; // The values are correct - expect(quotient).toBe(divisor === 0 ? 0 : Math.floor(dividend / divisor)); - expect(remainder).toBe(divisor === 0 ? dividend : dividend % divisor); + expect(quotient).toBe( + divisor === 0 ? 0 : Math.floor(dividend / divisor) + ); + expect(remainder).toBe( + divisor === 0 ? dividend : dividend % divisor + ); // The outputs of the nodes are correct expect(quotientOutput).toStrictEqual(fnQuotient); @@ -196,10 +206,15 @@ describe("NodeExecutor", () => { it("should execute a `node-trigger` (CRON)", async () => { const node = await service.findById(db.graph.nodes[12]._id); expect(node.behavior.type).toBe(NodeBehaviorType.TRIGGER); - expect((node.behavior as NodeBehaviorTrigger).trigger.type).toBe(NodeTriggerType.CRON); + expect((node.behavior as NodeBehaviorTrigger).trigger.type).toBe( + NodeTriggerType.CRON + ); - const now = new Date().getTime(); - const outputs = await executor.execute({ node: node, valuedInputs: new Map() }); + const now = Date.now(); + const outputs = await executor.execute({ + node: node, + valuedInputs: new Map() + }); expect(outputs).toHaveLength(1); const [{ output, value }] = outputs; @@ -215,7 +230,10 @@ describe("NodeExecutor", () => { const node = await service.findById(_id); expect(node.behavior.type).toBe(NodeBehaviorType.VARIABLE); - const outputValues = await executor.execute({ node: node, valuedInputs: new Map() }); + const outputValues = await executor.execute({ + node: node, + valuedInputs: new Map() + }); expect(outputValues).toHaveLength(1); const [{ output: outputValue, value }] = outputValues; @@ -251,8 +269,15 @@ describe("NodeExecutor", () => { it("should execute a `node-function` reference ('Integer division')", async () => { const node = await service.create({ - behavior: { __node: db.graph.nodes[7]._id, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node: db.graph.nodes[7]._id, + type: NodeBehaviorType.REFERENCE + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "fn ref" }); expect(node.behavior.type).toBe(NodeBehaviorType.REFERENCE); @@ -275,7 +300,9 @@ describe("NodeExecutor", () => { }); // The values are correct - expect(quotient).toBe(divisor === 0 ? 0 : Math.floor(dividend / divisor)); + expect(quotient).toBe( + divisor === 0 ? 0 : Math.floor(dividend / divisor) + ); expect(remainder).toBe(dividend % divisor); // The outputs of the nodes are correct @@ -286,15 +313,27 @@ describe("NodeExecutor", () => { it("should execute a `node-variable` reference (~= global variable)", async () => { const nodeRef = await service.findById(db.graph.nodes[0]._id); const node = await service.create({ - behavior: { __node: nodeRef._id, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node: nodeRef._id, + type: NodeBehaviorType.REFERENCE + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "var ref" }); expect(nodeRef.behavior.type).toBe(NodeBehaviorType.VARIABLE); expect(node.behavior.type).toBe(NodeBehaviorType.REFERENCE); - const [{ output, value }] = await executor.execute({ node, valuedInputs: new Map() }); - expect(value).toBe((nodeRef.behavior as NodeBehaviorVariable).value); + const [{ output, value }] = await executor.execute({ + node, + valuedInputs: new Map() + }); + expect(value).toBe( + (nodeRef.behavior as NodeBehaviorVariable).value + ); expect(output).toStrictEqual(node.outputs[0]); }); }); diff --git a/apps/backend/src/app/node/executor/node.executor.ts b/apps/backend/src/app/node/executor/node.executor.ts index cfc70099..9c91b880 100644 --- a/apps/backend/src/app/node/executor/node.executor.ts +++ b/apps/backend/src/app/node/executor/node.executor.ts @@ -2,7 +2,12 @@ import { forwardRef, Inject, Injectable } from "@nestjs/common"; import { filter, lastValueFrom, toArray } from "rxjs"; import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; import { NodeTriggerType } from "~/lib/common/app/node/dtos/behaviors/triggers"; -import { castNodeIoValueTo, NODE_IO_VOID, NodeIoType, NodeIoValue } from "~/lib/common/app/node/io"; +import { + castNodeIoValueTo, + NODE_IO_VOID, + NodeIoType, + NodeIoValue +} from "~/lib/common/app/node/io"; import { EntityId } from "~/lib/common/dtos/entity"; import { NodeExecutorMissingInputException } from "./exceptions"; @@ -95,7 +100,9 @@ export class NodeExecutor { * @param params The parameters to execute a node * @returns All the {@link NodeOutputEntity} affected with their value */ - public async execute(params: NodeExecuteParameters): Promise { + public async execute( + params: NodeExecuteParameters + ): Promise { const { node, valuedInputs } = params; const { behavior, inputs, outputs } = node; @@ -130,7 +137,9 @@ export class NodeExecutor { } const [output] = outputs; - return [{ output, value: castNodeIoValueTo(output.type, value) }]; + return [ + { output, value: castNodeIoValueTo(output.type, value) } + ]; } case NodeBehaviorType.FUNCTION: @@ -140,7 +149,12 @@ export class NodeExecutor { return this.executeTrigger(behavior, node); case NodeBehaviorType.VARIABLE: - return [{ output: outputs[0], value: this.executeVariable(behavior) }]; + return [ + { + output: outputs[0], + value: this.executeVariable(behavior) + } + ]; case NodeBehaviorType.REFERENCE: return this.executeReference(behavior, node, getValues()); @@ -170,7 +184,10 @@ export class NodeExecutor { } } - private executeCode(behavior: NodeBehaviorCode, inputs: readonly NodeIoValue[]) { + private executeCode( + behavior: NodeBehaviorCode, + inputs: readonly NodeIoValue[] + ) { // TODO: better type Fn = (...inputs: NodeIoValue[]) => Promise; return (eval(behavior.code) as Fn)(...inputs); @@ -181,17 +198,24 @@ export class NodeExecutor { node: NodeEntity, inputs: NodeInputAndValues ): Promise { - const { data: parametersIn } = await this.service.findByGraph(behavior.__graph, { - $or: [ - { behavior: { type: NodeBehaviorType.PARAMETER_IN } }, - { - behavior: { - node: { behavior: { type: NodeBehaviorType.PARAMETER_IN } }, - type: NodeBehaviorType.REFERENCE + const { data: parametersIn } = await this.service.findByGraph( + behavior.__graph, + { + $or: [ + { behavior: { type: NodeBehaviorType.PARAMETER_IN } }, + { + behavior: { + node: { + behavior: { + type: NodeBehaviorType.PARAMETER_IN + } + }, + type: NodeBehaviorType.REFERENCE + } } - } - ] - }); + ] + } + ); // TODO: a way to access the observable from the outside? // Get the state observable @@ -209,8 +233,11 @@ export class NodeExecutor { return lastValueFrom( executeState$.pipe( filter( - (state: GraphExecuteState): state is GraphExecuteResolutionEndState => - state.type === "resolution-end" && outputIds.includes(state.node._id) + ( + state: GraphExecuteState + ): state is GraphExecuteResolutionEndState => + state.type === "resolution-end" && + outputIds.includes(state.node._id) ), toArray() ) @@ -224,30 +251,36 @@ export class NodeExecutor { ): Promise { return this.execute({ node: await this.service.findById(behavior.__node), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- An error will be thrown if there is a missing input - valuedInputs: new Map(valuedInputs.map(({ input, value }) => [input.__ref!, value])) + + valuedInputs: new Map( + valuedInputs.map(({ input, value }) => [input.__ref!, value]) + ) }).then(outputs => { const values = new Map( outputs.map(({ output, value }) => [output._id, value] as const) ); - return ( - node.outputs - .getItems() - .filter(({ __ref }) => __ref && values.has(__ref)) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Ok thanks from `filter` - .map(output => ({ output, value: values.get(output.__ref!)! })) - ); + return node.outputs + .getItems() + .filter(({ __ref }) => __ref && values.has(__ref)) + + .map(output => ({ + output, + value: values.get(output.__ref!)! + })); }); } - private executeTrigger(behavior: NodeBehaviorTrigger, node: NodeEntity): NodeOutputAndValue[] { + private executeTrigger( + behavior: NodeBehaviorTrigger, + node: NodeEntity + ): NodeOutputAndValue[] { const { trigger } = behavior; const { outputs } = node; switch (trigger.type) { case NodeTriggerType.CRON: - return [{ output: outputs[0], value: new Date().getTime() }]; + return [{ output: outputs[0], value: Date.now() }]; } } diff --git a/apps/backend/src/app/node/input/exceptions/node-input.readonly.exception.ts b/apps/backend/src/app/node/input/exceptions/node-input.readonly.exception.ts index ca94a516..b5fe1972 100644 --- a/apps/backend/src/app/node/input/exceptions/node-input.readonly.exception.ts +++ b/apps/backend/src/app/node/input/exceptions/node-input.readonly.exception.ts @@ -1,5 +1,8 @@ import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; -import { NodeErrorCode, NodeInputReadonlyErrorCode } from "~/lib/common/app/node/error-codes"; +import { + NodeErrorCode, + NodeInputReadonlyErrorCode +} from "~/lib/common/app/node/error-codes"; import { NodeException } from "../../exceptions"; @@ -18,7 +21,9 @@ export class NodeInputReadonlyException extends NodeException { errorCode: NodeInputReadonlyErrorCode, behaviorType: NodeBehaviorType ) { - return `A node '${behaviorType}' can not ${this.getVerb(errorCode)} its inputs`; + return `A node '${behaviorType}' can not ${this.getVerb( + errorCode + )} its inputs`; } private static getVerb(errorCode: NodeInputReadonlyErrorCode): string { @@ -38,7 +43,13 @@ export class NodeInputReadonlyException extends NodeException { * @param errorCode to use * @param behaviorType trying to update its inputs */ - public constructor(errorCode: NodeInputReadonlyErrorCode, behaviorType: NodeBehaviorType) { - super(errorCode, NodeInputReadonlyException.createMessage(errorCode, behaviorType)); + public constructor( + errorCode: NodeInputReadonlyErrorCode, + behaviorType: NodeBehaviorType + ) { + super( + errorCode, + NodeInputReadonlyException.createMessage(errorCode, behaviorType) + ); } } diff --git a/apps/backend/src/app/node/input/node-input.controller.ts b/apps/backend/src/app/node/input/node-input.controller.ts index 188664f6..7ad80022 100644 --- a/apps/backend/src/app/node/input/node-input.controller.ts +++ b/apps/backend/src/app/node/input/node-input.controller.ts @@ -1,11 +1,22 @@ -import { Body, Controller, Delete, Param, Patch, Post, UseInterceptors } from "@nestjs/common"; +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + UseInterceptors +} from "@nestjs/common"; import { ApiCreatedResponse, ApiOkResponse, ApiTags } from "@nestjs/swagger"; import { NodeInputCreateDto, NodeInputDto, NodeInputUpdateDto } from "~/lib/common/app/node/dtos/input"; -import { generateNodeInputsEndpoint, NodeInputEndpoint } from "~/lib/common/app/node/endpoints"; +import { + generateNodeInputsEndpoint, + NodeInputEndpoint +} from "~/lib/common/app/node/endpoints"; import { EntityId } from "~/lib/common/dtos/entity"; import { UnshiftParameters } from "~/lib/common/types"; @@ -13,7 +24,11 @@ import { NodeInputEntity } from "./node-input.entity"; import { NodeInputService } from "./node-input.service"; import { UseAuth } from "../../auth/auth.guard"; import { NodeEntity } from "../node.entity"; -import { ApiNodeParam, NodeInterceptedParam, NodeInterceptor } from "../node.interceptor"; +import { + ApiNodeParam, + NodeInterceptedParam, + NodeInterceptor +} from "../node.interceptor"; /** @internal */ type EndpointBase = NodeInputEndpoint; @@ -27,7 +42,11 @@ type EndpointTransformed = { * {@link NodeInputEntity} controller */ @ApiTags("Node inputs") -@Controller(generateNodeInputsEndpoint(`:${NodeInterceptor.PATH_PARAM}` as unknown as EntityId)) +@Controller( + generateNodeInputsEndpoint( + `:${NodeInterceptor.PATH_PARAM}` as unknown as EntityId + ) +) @UseAuth() @UseInterceptors(NodeInterceptor) export class NodeInputController implements EndpointTransformed { @@ -41,7 +60,10 @@ export class NodeInputController implements EndpointTransformed { @ApiCreatedResponse({ type: NodeInputDto }) @ApiNodeParam() @Post() - public create(@NodeInterceptedParam() node: NodeEntity, @Body() body: NodeInputCreateDto) { + public create( + @NodeInterceptedParam() node: NodeEntity, + @Body() body: NodeInputCreateDto + ) { return this.service.createFromNode(node, body); } @@ -59,7 +81,10 @@ export class NodeInputController implements EndpointTransformed { @ApiNodeParam() @ApiOkResponse({ type: NodeInputDto }) @Delete("/:id") - public delete(@NodeInterceptedParam() node: NodeEntity, @Param("id") id: number) { + public delete( + @NodeInterceptedParam() node: NodeEntity, + @Param("id") id: number + ) { return this.service.deleteFromNode(node, id); } } diff --git a/apps/backend/src/app/node/input/node-input.entity.ts b/apps/backend/src/app/node/input/node-input.entity.ts index 1e2376e7..e7cf2856 100644 --- a/apps/backend/src/app/node/input/node-input.entity.ts +++ b/apps/backend/src/app/node/input/node-input.entity.ts @@ -20,7 +20,10 @@ const NodeProperty = ManyToOneFactory(() => NodeEntity, { * The entity for a `node-input` */ @Entity({ customRepository: () => NodeInputRepository }) -export class NodeInputEntity extends EntityBase implements DtoToEntity { +export class NodeInputEntity + extends EntityBase + implements DtoToEntity +{ /** @inheritDoc */ @NodeProperty({ foreign: false }) public __node!: number; diff --git a/apps/backend/src/app/node/input/node-input.service.spec.ts b/apps/backend/src/app/node/input/node-input.service.spec.ts index 39732704..5b1a8ba9 100644 --- a/apps/backend/src/app/node/input/node-input.service.spec.ts +++ b/apps/backend/src/app/node/input/node-input.service.spec.ts @@ -1,6 +1,9 @@ import { Test } from "@nestjs/testing"; import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; -import { NodeInputCreateDto, NodeInputUpdateDto } from "~/lib/common/app/node/dtos/input"; +import { + NodeInputCreateDto, + NodeInputUpdateDto +} from "~/lib/common/app/node/dtos/input"; import { NodeIoType } from "~/lib/common/app/node/io"; import { ONLY_NODES_SEED } from "~/lib/common/seeds"; import { omit } from "~/lib/common/utils/object-fns"; @@ -47,8 +50,12 @@ describe("NodeInputService", () => { expect(nodeCode.behavior.type).toBe(NodeBehaviorType.CODE); expect(nodeTrigger.behavior.type).toBe(NodeBehaviorType.TRIGGER); expect(nodeFunction.behavior.type).toBe(NodeBehaviorType.FUNCTION); - expect(nodeParameterIn.behavior.type).toBe(NodeBehaviorType.PARAMETER_IN); - expect(nodeParameterOut.behavior.type).toBe(NodeBehaviorType.PARAMETER_OUT); + expect(nodeParameterIn.behavior.type).toBe( + NodeBehaviorType.PARAMETER_IN + ); + expect(nodeParameterOut.behavior.type).toBe( + NodeBehaviorType.PARAMETER_OUT + ); expect(nodeReference.behavior.type).toBe(NodeBehaviorType.REFERENCE); return { @@ -68,7 +75,10 @@ describe("NodeInputService", () => { it("should create an input for a node (CODE)", async () => { const { nodeCode } = await getAllNodes(); - const toCreate: NodeInputCreateDto = { name: "1", type: NodeIoType.STRING }; + const toCreate: NodeInputCreateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of [nodeCode]) { const beforeLength = node.inputs.length; const updated = await service.createFromNode(node, toCreate); @@ -78,18 +88,23 @@ describe("NodeInputService", () => { const { inputs } = await nodeService.findById(node._id); expect(inputs).toHaveLength(beforeLength + 1); - expect(inputs.getItems().some(({ _id }) => _id === updated._id)).toBeTrue(); + expect( + inputs.getItems().some(({ _id }) => _id === updated._id) + ).toBe(true); } }); it("should fail when trying to create readonly inputs", async () => { const nodes = await getAllNodes(); - const toCreate: NodeInputCreateDto = { name: "1", type: NodeIoType.STRING }; + const toCreate: NodeInputCreateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of Object.values(omit(nodes, ["nodeCode"]))) { - await expect(() => service.createFromNode(node, toCreate)).rejects.toThrow( - NodeInputReadonlyException - ); + await expect(() => + service.createFromNode(node, toCreate) + ).rejects.toThrow(NodeInputReadonlyException); } }); }); @@ -100,10 +115,17 @@ describe("NodeInputService", () => { it("should update the input of a node (CODE & PARAMETER_OUT)", async () => { const { nodeCode, nodeParameterOut } = await getAllNodes(); - const toUpdate: NodeInputUpdateDto = { name: "1", type: NodeIoType.STRING }; + const toUpdate: NodeInputUpdateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of [nodeCode, nodeParameterOut]) { const [input] = node.inputs.getItems(); - const updated = await service.updateFromNode(node, input._id, toUpdate); + const updated = await service.updateFromNode( + node, + input._id, + toUpdate + ); expect(updated.name).toBe(toUpdate.name); expect(updated.type).toBe(toUpdate.type); @@ -111,9 +133,13 @@ describe("NodeInputService", () => { }); it("should fail when trying to update readonly inputs", async () => { - const { nodeFunction, nodeReference, nodeVariable } = await getAllNodes(); + const { nodeFunction, nodeReference, nodeVariable } = + await getAllNodes(); - const toUpdate: NodeInputUpdateDto = { name: "1", type: NodeIoType.STRING }; + const toUpdate: NodeInputUpdateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of [nodeFunction, nodeReference, nodeVariable]) { const [input] = node.inputs.getItems(); await expect(() => @@ -130,19 +156,33 @@ describe("NodeInputService", () => { const { nodeCode } = await getAllNodes(); for (const node of [nodeCode]) { const beforeLength = node.inputs.length; - const deleted = await service.deleteFromNode(node, node.inputs.getItems()[0]._id); + const deleted = await service.deleteFromNode( + node, + node.inputs.getItems()[0]._id + ); const { inputs } = await nodeService.findById(node._id); expect(inputs).toHaveLength(beforeLength - 1); - expect(inputs.getItems().some(({ _id }) => _id === deleted._id)).toBeFalse(); + expect( + inputs.getItems().some(({ _id }) => _id === deleted._id) + ).toBe(false); } }); it("should fail when trying to delete readonly inputs", async () => { - const { nodeFunction, nodeParameterOut, nodeReference, nodeVariable } = - await getAllNodes(); - - for (const node of [nodeFunction, nodeParameterOut, nodeReference, nodeVariable]) { + const { + nodeFunction, + nodeParameterOut, + nodeReference, + nodeVariable + } = await getAllNodes(); + + for (const node of [ + nodeFunction, + nodeParameterOut, + nodeReference, + nodeVariable + ]) { await expect(() => service.deleteFromNode(node, node.inputs.getItems()[0]._id) ).rejects.toThrow(NodeInputReadonlyException); diff --git a/apps/backend/src/app/node/input/node-input.service.ts b/apps/backend/src/app/node/input/node-input.service.ts index e97b26e1..024ec974 100644 --- a/apps/backend/src/app/node/input/node-input.service.ts +++ b/apps/backend/src/app/node/input/node-input.service.ts @@ -1,5 +1,8 @@ import { Injectable } from "@nestjs/common"; -import { NodeInputCreateDto, NodeInputUpdateDto } from "~/lib/common/app/node/dtos/input"; +import { + NodeInputCreateDto, + NodeInputUpdateDto +} from "~/lib/common/app/node/dtos/input"; import { NodeErrorCode } from "~/lib/common/app/node/error-codes"; import { areNodeInputsReadonlyOnCreate, @@ -69,7 +72,10 @@ export class NodeInputService { inputId: number, options?: EntityServiceFindOptions ) { - return this.entityService.findOne({ __node: nodeId, _id: inputId }, options); + return this.entityService.findOne( + { __node: nodeId, _id: inputId }, + options + ); } /** @@ -86,7 +92,10 @@ export class NodeInputService { ): Promise { const type = node.behavior.type; if (areNodeInputsReadonlyOnCreate(type)) { - throw new NodeInputReadonlyException(NodeErrorCode.INPUTS_READONLY_CREATE, type); + throw new NodeInputReadonlyException( + NodeErrorCode.INPUTS_READONLY_CREATE, + type + ); } return this.entityService.create({ @@ -114,7 +123,10 @@ export class NodeInputService { const type = node.behavior.type; if (areNodeInputsReadonlyOnUpdate(type)) { - throw new NodeInputReadonlyException(NodeErrorCode.INPUTS_READONLY_UPDATE, type); + throw new NodeInputReadonlyException( + NodeErrorCode.INPUTS_READONLY_UPDATE, + type + ); } return this.entityService.update(id, toUpdate); @@ -128,12 +140,18 @@ export class NodeInputService { * @param id of the input to delete * @returns the deleted input */ - public async deleteFromNode(node: NodeEntity, id: EntityId): Promise { + public async deleteFromNode( + node: NodeEntity, + id: EntityId + ): Promise { await this.findOneWithNodeId(node._id, id); const type = node.behavior.type; if (areNodeInputsReadonlyOnDelete(type)) { - throw new NodeInputReadonlyException(NodeErrorCode.INPUTS_READONLY_DELETE, type); + throw new NodeInputReadonlyException( + NodeErrorCode.INPUTS_READONLY_DELETE, + type + ); } return this.entityService.delete(id); diff --git a/apps/backend/src/app/node/input/node-input.spec.ts b/apps/backend/src/app/node/input/node-input.spec.ts index 4afa1aac..bbcb1830 100644 --- a/apps/backend/src/app/node/input/node-input.spec.ts +++ b/apps/backend/src/app/node/input/node-input.spec.ts @@ -96,10 +96,19 @@ describe("NodeInput", () => { .then(async () => { const db = dbTestBase.db as typeof BASE_SEED; - const reference = await nodeService.findById(db.graph.nodes[0]._id); + const reference = await nodeService.findById( + db.graph.nodes[0]._id + ); const { inputs } = await nodeService.create({ - behavior: { __node: reference._id, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node: reference._id, + type: NodeBehaviorType.REFERENCE + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: `*${reference.name}` }); @@ -109,7 +118,9 @@ describe("NodeInput", () => { const inputRef = reference.inputs[i]; expect(input.__ref).toBe(inputRef._id); expect(input.type).toBe(inputRef.type); - expect(input._created_at).not.toStrictEqual(inputRef._created_at); + expect(input._created_at).not.toStrictEqual( + inputRef._created_at + ); } }) .then(() => dbTestBase.close()) diff --git a/apps/backend/src/app/node/input/node-input.types.ts b/apps/backend/src/app/node/input/node-input.types.ts index f45849af..88bd8d6d 100644 --- a/apps/backend/src/app/node/input/node-input.types.ts +++ b/apps/backend/src/app/node/input/node-input.types.ts @@ -2,4 +2,5 @@ import { NodeInputCreateDto } from "~/lib/common/app/node/dtos/input"; import { NodeInputEntity } from "./node-input.entity"; -export type NodeInputCreate = NodeInputCreateDto & Pick; +export type NodeInputCreate = NodeInputCreateDto & + Pick; diff --git a/apps/backend/src/app/node/kind/node-kind.base.entity.ts b/apps/backend/src/app/node/kind/node-kind.base.entity.ts index 79a8e5ce..83019788 100644 --- a/apps/backend/src/app/node/kind/node-kind.base.entity.ts +++ b/apps/backend/src/app/node/kind/node-kind.base.entity.ts @@ -1,5 +1,8 @@ import { Entity, Enum, OneToOne } from "@mikro-orm/core"; -import { NodeKindBaseDto, NodeKindDiscriminatorKey } from "~/lib/common/app/node/dtos/kind"; +import { + NodeKindBaseDto, + NodeKindDiscriminatorKey +} from "~/lib/common/app/node/dtos/kind"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { NodeEntity } from "../node.entity"; diff --git a/apps/backend/src/app/node/node.controller.ts b/apps/backend/src/app/node/node.controller.ts index c0c6da80..c17aeae8 100644 --- a/apps/backend/src/app/node/node.controller.ts +++ b/apps/backend/src/app/node/node.controller.ts @@ -1,4 +1,13 @@ -import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common"; +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query +} from "@nestjs/common"; import { ApiCreatedResponse, ApiOkResponse, ApiTags } from "@nestjs/swagger"; import { NodeCreateDto, @@ -7,7 +16,10 @@ import { NodeResultsDto, NodeUpdateDto } from "~/lib/common/app/node/dtos"; -import { NodeEndpoint, NODES_ENDPOINT_PREFIX } from "~/lib/common/app/node/endpoints"; +import { + NodeEndpoint, + NODES_ENDPOINT_PREFIX +} from "~/lib/common/app/node/endpoints"; import { NodeEntity } from "./node.entity"; import { NodeService } from "./node.service"; diff --git a/apps/backend/src/app/node/node.entity.ts b/apps/backend/src/app/node/node.entity.ts index f4d7b560..b84e73c2 100644 --- a/apps/backend/src/app/node/node.entity.ts +++ b/apps/backend/src/app/node/node.entity.ts @@ -59,7 +59,10 @@ export class NodeEntity extends EntityBase implements DtoToEntity { }) public readonly outputs = new Collection(this); - @ManyToMany(() => CategoryEntity, ({ nodes }) => nodes, { hidden: true, owner: true }) + @ManyToMany(() => CategoryEntity, ({ nodes }) => nodes, { + hidden: true, + owner: true + }) public readonly categories? = new Collection(this); /** @inheritDoc */ diff --git a/apps/backend/src/app/node/node.interceptor.ts b/apps/backend/src/app/node/node.interceptor.ts index c8adc1c5..c69c476b 100644 --- a/apps/backend/src/app/node/node.interceptor.ts +++ b/apps/backend/src/app/node/node.interceptor.ts @@ -14,9 +14,9 @@ import { Request } from "express"; import { NodeEntity } from "./node.entity"; import { NodeService } from "./node.service"; -// eslint-disable-next-line no-use-before-define -- Only for typing -type RequestParams = Partial> & - // eslint-disable-next-line no-use-before-define -- Only for typing +type RequestParams = Partial< + Record +> & Record; @Injectable() @@ -39,7 +39,9 @@ export class NodeInterceptor implements NestInterceptor { /** @inheritDoc */ public intercept(context: ExecutionContext, next: CallHandler) { - const request = context.switchToHttp().getRequest>(); + const request = context + .switchToHttp() + .getRequest>(); const nodeId = request.params[NodeInterceptor.PATH_PARAM]; const id = +nodeId; @@ -59,7 +61,10 @@ export class NodeInterceptor implements NestInterceptor { /** * Injects the intercepted Node from the {@link NodeInterceptor} into a parameter. */ -export const NodeInterceptedParam = createParamDecorator( +export const NodeInterceptedParam = createParamDecorator< + never, + ExecutionContext +>( (_, context) => context.switchToHttp().getRequest>().params[ NodeInterceptor.NODE_TOKEN @@ -73,7 +78,9 @@ export const NodeInterceptedParam = createParamDecorator = {}) => +export const ApiNodeParam = ( + options: Pick = {} +) => ApiParam({ ...options, name: NodeInterceptor.PATH_PARAM, diff --git a/apps/backend/src/app/node/node.module.ts b/apps/backend/src/app/node/node.module.ts index 3ab1bdc4..afc0b45d 100644 --- a/apps/backend/src/app/node/node.module.ts +++ b/apps/backend/src/app/node/node.module.ts @@ -17,7 +17,10 @@ import { GraphModule } from "../graph/graph.module"; @Module({ controllers: [NodeController, NodeInputController, NodeOutputController], exports: [NodeExecutor, NodeService], - imports: [forwardRef(() => GraphModule), MikroOrmModule.forFeature(NODE_ENTITIES)], + imports: [ + forwardRef(() => GraphModule), + MikroOrmModule.forFeature(NODE_ENTITIES) + ], providers: [NodeExecutor, NodeInputService, NodeOutputService, NodeService] }) export class NodeModule {} diff --git a/apps/backend/src/app/node/node.repository.ts b/apps/backend/src/app/node/node.repository.ts index 4e46a69f..14ccd7b2 100644 --- a/apps/backend/src/app/node/node.repository.ts +++ b/apps/backend/src/app/node/node.repository.ts @@ -1,4 +1,9 @@ -import { CreateOptions, EntityRepository, Reference, RequiredEntityData } from "@mikro-orm/core"; +import { + CreateOptions, + EntityRepository, + Reference, + RequiredEntityData +} from "@mikro-orm/core"; import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; import { NodeInputEntity } from "./input/node-input.entity"; @@ -18,20 +23,43 @@ export class NodeRepository extends EntityRepository { if (behavior.type === NodeBehaviorType.FUNCTION) { const graph = behavior.__graph ? Reference.createFromPK(GraphEntity, behavior.__graph) - : // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- It will create a graph in the ORM transaction - this.getEntityManager().create(GraphEntity, {} as GraphEntity); + : this.getEntityManager().create( + GraphEntity, + {} as unknown as never + ); - // @ts-expect-error - TS2589: Excessive type from the repository base code - return super.create({ ...data, behavior: { ...behavior, graph } }, options); + return super.create( + // @ts-expect-error -- TS2589: Excessive type from the repository base code + { ...data, behavior: { ...behavior, graph } }, + options + ); } - if (behavior.type === NodeBehaviorType.PARAMETER_IN && behavior.__node_input) { - const nodeInput = Reference.createFromPK(NodeInputEntity, behavior.__node_input); - return super.create({ ...data, behavior: { ...behavior, nodeInput } }, options); + if ( + behavior.type === NodeBehaviorType.PARAMETER_IN && + behavior.__node_input + ) { + const nodeInput = Reference.createFromPK( + NodeInputEntity, + behavior.__node_input + ); + return super.create( + { ...data, behavior: { ...behavior, nodeInput } }, + options + ); } - if (behavior.type === NodeBehaviorType.PARAMETER_OUT && behavior.__node_output) { - const nodeOutput = Reference.createFromPK(NodeOutputEntity, behavior.__node_output); - return super.create({ ...data, behavior: { ...behavior, nodeOutput } }, options); + if ( + behavior.type === NodeBehaviorType.PARAMETER_OUT && + behavior.__node_output + ) { + const nodeOutput = Reference.createFromPK( + NodeOutputEntity, + behavior.__node_output + ); + return super.create( + { ...data, behavior: { ...behavior, nodeOutput } }, + options + ); } return super.create(data, options); diff --git a/apps/backend/src/app/node/node.service.spec.ts b/apps/backend/src/app/node/node.service.spec.ts index 70571e4e..efdc9ee7 100644 --- a/apps/backend/src/app/node/node.service.spec.ts +++ b/apps/backend/src/app/node/node.service.spec.ts @@ -7,7 +7,10 @@ import { NodeTriggerType } from "~/lib/common/app/node/dtos/behaviors/triggers"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { BASE_SEED } from "~/lib/common/seeds"; -import { NodeNoTemplateParameterException, NodeReadonlyKindTypeException } from "./exceptions"; +import { + NodeNoTemplateParameterException, + NodeReadonlyKindTypeException +} from "./exceptions"; import { NodeInputRepository } from "./input/node-input.repository"; import { NodeKindVertexEntity } from "./kind"; import { NodeModule } from "./node.module"; @@ -83,12 +86,16 @@ describe("NodeService", () => { return { categories: await Promise.all( [category1, category2].map(({ _id }) => - categoryService.findById(_id, { populate: { nodes: true } }) + categoryService.findById(_id, { + populate: { nodes: true } + }) ) ), nodes: await Promise.all( [node1, node2].map(({ _id }) => - service.findById(_id, { populate: { categories: true } }) + service.findById(_id, { + populate: { categories: true } + }) ) ) }; @@ -138,8 +145,12 @@ describe("NodeService", () => { expect(node1.categories.getItems()[0]._id).toBe(catA._id); - const cat1 = await categoryService.findById(catA._id, { populate: { nodes: true } }); - const cat2 = await categoryService.findById(catB._id, { populate: { nodes: true } }); + const cat1 = await categoryService.findById(catA._id, { + populate: { nodes: true } + }); + const cat2 = await categoryService.findById(catB._id, { + populate: { nodes: true } + }); expect(cat1.nodes).toHaveLength(1); expect(cat2.nodes).toHaveLength(0); @@ -154,8 +165,12 @@ describe("NodeService", () => { await categoryService.delete(catA._id); - const node1 = await service.findById(nodeA._id, { populate: { categories: true } }); - const node2 = await service.findById(nodeB._id, { populate: { categories: true } }); + const node1 = await service.findById(nodeA._id, { + populate: { categories: true } + }); + const node2 = await service.findById(nodeB._id, { + populate: { categories: true } + }); expect(node1.categories).toHaveLength(1); expect(node2.categories).toHaveLength(0); @@ -170,8 +185,12 @@ describe("NodeService", () => { await service.delete(nodeA._id); - const cat1 = await categoryService.findById(catA._id, { populate: { nodes: true } }); - const cat2 = await categoryService.findById(catB._id, { populate: { nodes: true } }); + const cat1 = await categoryService.findById(catA._id, { + populate: { nodes: true } + }); + const cat2 = await categoryService.findById(catB._id, { + populate: { nodes: true } + }); expect(cat1.nodes).toHaveLength(1); expect(cat2.nodes).toHaveLength(0); @@ -189,7 +208,10 @@ describe("NodeService", () => { await expect(() => service.create({ behavior: { - trigger: { cron: "* * * * *", type: NodeTriggerType.CRON }, + trigger: { + cron: "* * * * *", + type: NodeTriggerType.CRON + }, type: NodeBehaviorType.TRIGGER }, kind: { @@ -209,7 +231,10 @@ describe("NodeService", () => { await expect(() => service.create({ behavior: { - trigger: { cron: "* * * * *", type: NodeTriggerType.CRON }, + trigger: { + cron: "* * * * *", + type: NodeTriggerType.CRON + }, type: NodeBehaviorType.TRIGGER }, kind: { @@ -243,7 +268,10 @@ describe("NodeService", () => { const { inputs, outputs } = node; const { data: arcs } = await graphArcService.findAndCount({ - $or: [{ from: { node: { _id: node._id } } }, { to: { node: { _id: node._id } } }] + $or: [ + { from: { node: { _id: node._id } } }, + { to: { node: { _id: node._id } } } + ] }); // Need to have some data before @@ -256,7 +284,9 @@ describe("NodeService", () => { // search by ids so that is not linked with the foreign keys const { pagination: { total: totalArcs } - } = await graphArcService.findAndCount({ _id: { $in: arcs.map(({ _id }) => _id) } }); + } = await graphArcService.findAndCount({ + _id: { $in: arcs.map(({ _id }) => _id) } + }); expect(totalArcs).toBe(0); for (const [repository, entities] of [ @@ -301,7 +331,9 @@ describe("NodeService", () => { expect(node.kind.type).toBe(NodeKindType.VERTEX); await expect(() => - service.update(node._id, { kind: { active: true, type: NodeKindType.TEMPLATE } }) + service.update(node._id, { + kind: { active: true, type: NodeKindType.TEMPLATE } + }) ).rejects.toThrow(NodeReadonlyKindTypeException); }); }); @@ -311,16 +343,20 @@ describe("NodeService", () => { beforeEach(() => dbTest.refresh()); it("should get one", async () => { - // eslint-disable-next-line unused-imports/no-unused-vars -- to remove from object - for (const { __categories: _, ...node } of dbTest.db.graph.nodes) { + for (const { __categories: _, ...node } of dbTest.db.graph + .nodes) { const row = await service.findById(node._id); expect(row.toJSON()).toStrictEqual(node); } }); it("should fail when getting one by an unknown id", async () => { - const id = Math.max(...dbTest.db.graph.nodes.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + const id = + Math.max(...dbTest.db.graph.nodes.map(({ _id }) => _id)) + + 1; + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); }); @@ -359,7 +395,9 @@ describe("NodeService", () => { // Update an entity and check its content const [node] = dbTest.db.graph.nodes; - const toUpdate: NodeUpdateDto = { name: `${node.name}-${node.name}` }; + const toUpdate: NodeUpdateDto = { + name: `${node.name}-${node.name}` + }; const updated = await service.update(node._id, toUpdate); expect(updated.name).toBe(toUpdate.name); @@ -387,8 +425,12 @@ describe("NodeService", () => { kind: { position, type: NodeKindType.VERTEX } }); - expect((updated as unknown as GraphNodeJSON).kind.position.x).toBe(position.x); - expect((updated as unknown as GraphNodeJSON).kind.position.y).toBe(position.y); + expect( + (updated as unknown as GraphNodeJSON).kind.position.x + ).toBe(position.x); + expect( + (updated as unknown as GraphNodeJSON).kind.position.y + ).toBe(position.y); }); }); @@ -414,7 +456,9 @@ describe("NodeService", () => { }); it("should not delete an unknown id", async () => { - const id = Math.max(...dbTest.db.graph.nodes.map(({ _id }) => _id)) + 1; + const id = + Math.max(...dbTest.db.graph.nodes.map(({ _id }) => _id)) + + 1; await expect(service.delete(id)).rejects.toThrow(NotFoundError); }); }); diff --git a/apps/backend/src/app/node/node.service.ts b/apps/backend/src/app/node/node.service.ts index 06fa8686..544aa584 100644 --- a/apps/backend/src/app/node/node.service.ts +++ b/apps/backend/src/app/node/node.service.ts @@ -12,10 +12,20 @@ import { NodeIoType } from "~/lib/common/app/node/io"; import { EntityId } from "~/lib/common/dtos/entity"; import { DtoToEntity } from "~/lib/common/dtos/entity/entity.types"; import { FindResultsDto } from "~/lib/common/dtos/find-results.dto"; -import { EntitiesToPopulate, EntityFilter, EntityFindParams } from "~/lib/common/endpoints"; +import { + EntitiesToPopulate, + EntityFilter, + EntityFindParams +} from "~/lib/common/endpoints"; -import { NodeBehaviorParameterInput, NodeBehaviorParameterOutput } from "./behaviors"; -import { NodeNoTemplateParameterException, NodeReadonlyKindTypeException } from "./exceptions"; +import { + NodeBehaviorParameterInput, + NodeBehaviorParameterOutput +} from "./behaviors"; +import { + NodeNoTemplateParameterException, + NodeReadonlyKindTypeException +} from "./exceptions"; import { NodeInputEntity } from "./input/node-input.entity"; import { NodeInputCreate } from "./input/node-input.types"; import { NODE_KIND_ENTITIES, NodeKindEntity } from "./kind"; @@ -66,14 +76,19 @@ export class NodeService eventManager.registerSubscriber({ getSubscribedEntities: () => [...NODE_KIND_ENTITIES], - beforeUpdate(args: EventArgs): Promise | void { + beforeUpdate( + args: EventArgs + ): Promise | void { const { changeSet } = args; if (!changeSet) { return; } const { entity, originalEntity } = changeSet; - if (!originalEntity?.type || originalEntity.type !== entity.type) { + if ( + !originalEntity?.type || + originalEntity.type !== entity.type + ) { throw new NodeReadonlyKindTypeException(); } } @@ -97,16 +112,22 @@ export class NodeService throw new NodeNoTemplateParameterException(); } - if (behavior.type !== NodeBehaviorType.TRIGGER || kind.type !== NodeKindType.VERTEX) { + if ( + behavior.type !== NodeBehaviorType.TRIGGER || + kind.type !== NodeKindType.VERTEX + ) { // Nothing to verify it the node is not a `trigger` return; } // TODO: A way to add custom relation in the EntityRelationsKey? const behaviorRelation: keyof GraphEntity = "nodeBehavior"; - const { nodeBehavior, workflow } = await this.graphService.findById(kind.__graph, { - populate: { [behaviorRelation]: true, workflow: true } - }); + const { nodeBehavior, workflow } = await this.graphService.findById( + kind.__graph, + { + populate: { [behaviorRelation]: true, workflow: true } + } + ); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- exists (reversed relation) if (nodeBehavior) { @@ -124,7 +145,7 @@ export class NodeService { limit: 0 } ); - if (total > 0) { + if (0 < total) { throw new GraphNodeTriggerInWorkflowException(); } } @@ -148,11 +169,21 @@ export class NodeService ) { // GraphNodeDto return this.findAndCount( - { $and: [{ kind: { __graph: graphId, type: NodeKindType.VERTEX } }, where] }, + { + $and: [ + { kind: { __graph: graphId, type: NodeKindType.VERTEX } }, + where + ] + }, params, options ) as Promise< - FindResultsDto>, P>> + FindResultsDto< + EntityLoaded< + DtoToEntity>, + P + > + > >; } @@ -181,7 +212,7 @@ export class NodeService } satisfies NodeOutputCreate as never); } - if (outputs.length + inputs.length > 0) { + if (0 < outputs.length + inputs.length) { await em.flush(); // "Refresh" relations @@ -202,12 +233,16 @@ export class NodeService * @returns The updated node */ public addCategory(nodeId: EntityId, categoryId: EntityId) { - return this.findById(nodeId, { populate: { categories: true } }).then(async node => { - node.categories.add(Reference.createFromPK(CategoryEntity, categoryId)); + return this.findById(nodeId, { populate: { categories: true } }).then( + async node => { + node.categories.add( + Reference.createFromPK(CategoryEntity, categoryId) + ); - await this.repository.getEntityManager().persistAndFlush(node); - return node; - }); + await this.repository.getEntityManager().persistAndFlush(node); + return node; + } + ); } /** @@ -219,20 +254,22 @@ export class NodeService * @returns The updated node */ public removeCategory(nodeId: EntityId, categoryId: EntityId) { - return this.findById(nodeId, { populate: { categories: true } }).then(async node => { - const categories = await node.categories.matching({ - where: { _id: { $eq: categoryId } } - }); + return this.findById(nodeId, { populate: { categories: true } }).then( + async node => { + const categories = await node.categories.matching({ + where: { _id: { $eq: categoryId } } + }); + + if (categories.length === 0) { + return node; + } + + node.categories.remove(categories[0]); - if (categories.length === 0) { + await this.repository.getEntityManager().persistAndFlush(node); return node; } - - node.categories.remove(categories[0]); - - await this.repository.getEntityManager().persistAndFlush(node); - return node; - }); + ); } /** @@ -309,7 +346,9 @@ export class NodeService * @param toCreate the initial value * @returns the transformed (when needed) data */ - private async transformBeforeCreate(toCreate: NodeCreateEntity): Promise { + private async transformBeforeCreate( + toCreate: NodeCreateEntity + ): Promise { const { behavior, kind } = toCreate; if ( @@ -320,7 +359,10 @@ export class NodeService // This will create empty entities, so the ORM create the missing relation during the transaction. // Linked to the repository create function const { data } = await this.findAndCount({ - behavior: { __graph: kind.__graph, type: NodeBehaviorType.FUNCTION } + behavior: { + __graph: kind.__graph, + type: NodeBehaviorType.FUNCTION + } }); if (data[0]?.behavior.type !== NodeBehaviorType.FUNCTION) { // Let the other validation throw the error @@ -340,12 +382,18 @@ export class NodeService behavior.type === NodeBehaviorType.PARAMETER_IN ? ({ ...behavior, - nodeInput: em.create(NodeInputEntity, ioToCreate as NodeInputEntity) + nodeInput: em.create( + NodeInputEntity, + ioToCreate as NodeInputEntity + ) } satisfies NodeBehaviorParameterInputCreateDto & Pick) : ({ ...behavior, - nodeOutput: em.create(NodeOutputEntity, ioToCreate as NodeOutputEntity) + nodeOutput: em.create( + NodeOutputEntity, + ioToCreate as NodeOutputEntity + ) } satisfies NodeBehaviorParameterOutputCreateDto & Pick); diff --git a/apps/backend/src/app/node/output/exceptions/node-output.readonly.exception.ts b/apps/backend/src/app/node/output/exceptions/node-output.readonly.exception.ts index 965a1a84..68619fef 100644 --- a/apps/backend/src/app/node/output/exceptions/node-output.readonly.exception.ts +++ b/apps/backend/src/app/node/output/exceptions/node-output.readonly.exception.ts @@ -1,5 +1,8 @@ import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; -import { NodeErrorCode, NodeOutputReadonlyErrorCode } from "~/lib/common/app/node/error-codes"; +import { + NodeErrorCode, + NodeOutputReadonlyErrorCode +} from "~/lib/common/app/node/error-codes"; import { NodeException } from "../../exceptions"; @@ -18,7 +21,9 @@ export class NodeOutputReadonlyException extends NodeException { errorCode: NodeOutputReadonlyErrorCode, behaviorType: NodeBehaviorType ) { - return `A node '${behaviorType}' can not ${this.getVerb(errorCode)} its outputs`; + return `A node '${behaviorType}' can not ${this.getVerb( + errorCode + )} its outputs`; } private static getVerb(errorCode: NodeOutputReadonlyErrorCode): string { @@ -34,7 +39,13 @@ export class NodeOutputReadonlyException extends NodeException { * @param errorCode to use * @param behaviorType trying to update its outputs */ - public constructor(errorCode: NodeOutputReadonlyErrorCode, behaviorType: NodeBehaviorType) { - super(errorCode, NodeOutputReadonlyException.createMessage(errorCode, behaviorType)); + public constructor( + errorCode: NodeOutputReadonlyErrorCode, + behaviorType: NodeBehaviorType + ) { + super( + errorCode, + NodeOutputReadonlyException.createMessage(errorCode, behaviorType) + ); } } diff --git a/apps/backend/src/app/node/output/node-output.controller.ts b/apps/backend/src/app/node/output/node-output.controller.ts index 712ee556..61195e26 100644 --- a/apps/backend/src/app/node/output/node-output.controller.ts +++ b/apps/backend/src/app/node/output/node-output.controller.ts @@ -1,7 +1,19 @@ -import { Body, Controller, Param, Patch, UseInterceptors } from "@nestjs/common"; +import { + Body, + Controller, + Param, + Patch, + UseInterceptors +} from "@nestjs/common"; import { ApiOkResponse, ApiTags } from "@nestjs/swagger"; -import { NodeOutputDto, NodeOutputUpdateDto } from "~/lib/common/app/node/dtos/output"; -import { generateNodeOutputsEndpoint, NodeOutputEndpoint } from "~/lib/common/app/node/endpoints"; +import { + NodeOutputDto, + NodeOutputUpdateDto +} from "~/lib/common/app/node/dtos/output"; +import { + generateNodeOutputsEndpoint, + NodeOutputEndpoint +} from "~/lib/common/app/node/endpoints"; import { EntityId } from "~/lib/common/dtos/entity"; import { UnshiftParameters } from "~/lib/common/types"; @@ -9,7 +21,11 @@ import { NodeOutputEntity } from "./node-output.entity"; import { NodeOutputService } from "./node-output.service"; import { UseAuth } from "../../auth/auth.guard"; import { NodeEntity } from "../node.entity"; -import { ApiNodeParam, NodeInterceptedParam, NodeInterceptor } from "../node.interceptor"; +import { + ApiNodeParam, + NodeInterceptedParam, + NodeInterceptor +} from "../node.interceptor"; /** @internal */ type EndpointBase = NodeOutputEndpoint; @@ -23,7 +39,11 @@ type EndpointTransformed = { * {@link NodeOutputEntity} controller */ @ApiTags("Node outputs") -@Controller(generateNodeOutputsEndpoint(`:${NodeInterceptor.PATH_PARAM}` as unknown as EntityId)) +@Controller( + generateNodeOutputsEndpoint( + `:${NodeInterceptor.PATH_PARAM}` as unknown as EntityId + ) +) @UseAuth() @UseInterceptors(NodeInterceptor) export class NodeOutputController implements EndpointTransformed { diff --git a/apps/backend/src/app/node/output/node-output.entity.ts b/apps/backend/src/app/node/output/node-output.entity.ts index 56ea0958..c437f5d1 100644 --- a/apps/backend/src/app/node/output/node-output.entity.ts +++ b/apps/backend/src/app/node/output/node-output.entity.ts @@ -1,4 +1,10 @@ -import { Entity, Enum, LoadStrategy, ManyToOne, Property } from "@mikro-orm/core"; +import { + Entity, + Enum, + LoadStrategy, + ManyToOne, + Property +} from "@mikro-orm/core"; import { NodeOutputDto } from "~/lib/common/app/node/dtos/output"; import { NodeIoType } from "~/lib/common/app/node/io"; import { EntityId } from "~/lib/common/dtos/entity"; @@ -21,7 +27,10 @@ const NodeProperty = ManyToOneFactory(() => NodeEntity, { * The entity for a `node-input` */ @Entity({ customRepository: () => NodeOutputRepository }) -export class NodeOutputEntity extends EntityBase implements DtoToEntity { +export class NodeOutputEntity + extends EntityBase + implements DtoToEntity +{ /** @inheritDoc */ @NodeProperty({ foreign: false }) public __node!: number; diff --git a/apps/backend/src/app/node/output/node-output.service.spec.ts b/apps/backend/src/app/node/output/node-output.service.spec.ts index 2ef82433..3b1788d7 100644 --- a/apps/backend/src/app/node/output/node-output.service.spec.ts +++ b/apps/backend/src/app/node/output/node-output.service.spec.ts @@ -42,15 +42,24 @@ describe("NodeOutputService", () => { expect(nodeVar.behavior.type).toBe(NodeBehaviorType.VARIABLE); expect(nodeCode.behavior.type).toBe(NodeBehaviorType.CODE); - expect(nodeParameterIn.behavior.type).toBe(NodeBehaviorType.PARAMETER_IN); + expect(nodeParameterIn.behavior.type).toBe( + NodeBehaviorType.PARAMETER_IN + ); expect(nodeVar.outputs).not.toHaveLength(0); expect(nodeCode.outputs).not.toHaveLength(0); expect(nodeParameterIn.outputs).not.toHaveLength(0); - const toUpdate: NodeOutputUpdateDto = { name: "1", type: NodeIoType.STRING }; + const toUpdate: NodeOutputUpdateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of [nodeVar, nodeCode, nodeParameterIn]) { const [output] = node.outputs.getItems(); - const updated = await service.updateFromNode(node, output._id, toUpdate); + const updated = await service.updateFromNode( + node, + output._id, + toUpdate + ); expect(updated.name).toBe(toUpdate.name); expect(updated.type).toBe(toUpdate.type); @@ -64,12 +73,17 @@ describe("NodeOutputService", () => { expect(nodeTrigger.behavior.type).toBe(NodeBehaviorType.TRIGGER); expect(nodeFunction.behavior.type).toBe(NodeBehaviorType.FUNCTION); - expect(nodeReference.behavior.type).toBe(NodeBehaviorType.REFERENCE); + expect(nodeReference.behavior.type).toBe( + NodeBehaviorType.REFERENCE + ); expect(nodeTrigger.outputs).not.toHaveLength(0); expect(nodeFunction.outputs).not.toHaveLength(0); expect(nodeReference.outputs).not.toHaveLength(0); - const toUpdate: NodeOutputUpdateDto = { name: "1", type: NodeIoType.STRING }; + const toUpdate: NodeOutputUpdateDto = { + name: "1", + type: NodeIoType.STRING + }; for (const node of [nodeTrigger, nodeFunction, nodeReference]) { const [output] = node.outputs.getItems(); await expect(() => diff --git a/apps/backend/src/app/node/output/node-output.service.ts b/apps/backend/src/app/node/output/node-output.service.ts index 8a0b4785..31a49c7b 100644 --- a/apps/backend/src/app/node/output/node-output.service.ts +++ b/apps/backend/src/app/node/output/node-output.service.ts @@ -1,5 +1,8 @@ import { Injectable } from "@nestjs/common"; -import { NodeOutputCreateDto, NodeOutputUpdateDto } from "~/lib/common/app/node/dtos/output"; +import { + NodeOutputCreateDto, + NodeOutputUpdateDto +} from "~/lib/common/app/node/dtos/output"; import { NodeErrorCode } from "~/lib/common/app/node/error-codes"; import { areNodeOutputsReadonlyOnUpdate } from "~/lib/common/app/node/io/output"; import { EntityId } from "~/lib/common/dtos/entity"; @@ -58,7 +61,10 @@ export class NodeOutputService { outputId: number, options?: EntityServiceFindOptions ) { - return this.entityService.findOne({ __node: nodeId, _id: outputId }, options); + return this.entityService.findOne( + { __node: nodeId, _id: outputId }, + options + ); } /** @@ -79,7 +85,10 @@ export class NodeOutputService { const type = node.behavior.type; if (areNodeOutputsReadonlyOnUpdate(type)) { - throw new NodeOutputReadonlyException(NodeErrorCode.OUTPUTS_READONLY_UPDATE, type); + throw new NodeOutputReadonlyException( + NodeErrorCode.OUTPUTS_READONLY_UPDATE, + type + ); } return this.entityService.update(id, toUpdate); diff --git a/apps/backend/src/app/node/output/node-output.spec.ts b/apps/backend/src/app/node/output/node-output.spec.ts index 38fdd0cd..0fc7770b 100644 --- a/apps/backend/src/app/node/output/node-output.spec.ts +++ b/apps/backend/src/app/node/output/node-output.spec.ts @@ -102,10 +102,19 @@ describe("NodeOutput", () => { .then(async () => { const db = dbTestBase.db as typeof BASE_SEED; - const reference = await nodeService.findById(db.graph.nodes[0]._id); + const reference = await nodeService.findById( + db.graph.nodes[0]._id + ); const { outputs } = await nodeService.create({ - behavior: { __node: reference._id, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node: reference._id, + type: NodeBehaviorType.REFERENCE + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: `*${reference.name}` }); @@ -115,7 +124,9 @@ describe("NodeOutput", () => { const outputRef = reference.outputs[i]; expect(output.__ref).toBe(outputRef._id); expect(output.type).toBe(outputRef.type); - expect(output._created_at).not.toStrictEqual(outputRef._created_at); + expect(output._created_at).not.toStrictEqual( + outputRef._created_at + ); } }) .then(() => dbTestBase.close()) diff --git a/apps/backend/src/app/user/user.controller.ts b/apps/backend/src/app/user/user.controller.ts index e8a1a15c..e95777f8 100644 --- a/apps/backend/src/app/user/user.controller.ts +++ b/apps/backend/src/app/user/user.controller.ts @@ -17,7 +17,10 @@ import { UserResultsDto, UserUpdateDto } from "~/lib/common/app/user/dtos"; -import { UserEndpoint, USERS_ENDPOINT_PREFIX } from "~/lib/common/app/user/endpoints"; +import { + UserEndpoint, + USERS_ENDPOINT_PREFIX +} from "~/lib/common/app/user/endpoints"; import { UserEntity } from "./user.entity"; import { UserService } from "./user.service"; diff --git a/apps/backend/src/app/user/user.service.spec.ts b/apps/backend/src/app/user/user.service.spec.ts index e922c31f..8fbd105e 100644 --- a/apps/backend/src/app/user/user.service.spec.ts +++ b/apps/backend/src/app/user/user.service.spec.ts @@ -1,4 +1,7 @@ -import { NotFoundError, UniqueConstraintViolationException } from "@mikro-orm/core"; +import { + NotFoundError, + UniqueConstraintViolationException +} from "@mikro-orm/core"; import { Test, TestingModule } from "@nestjs/testing"; import { omit } from "~/lib/common/utils/object-fns"; @@ -34,13 +37,18 @@ describe("UserService", () => { it("should get one", async () => { for (const user of dbTest.db.users) { const row = await service.findById(user._id); - expect(row.toJSON()).toStrictEqual(omit(user, ["password"])); + expect(row.toJSON()).toStrictEqual( + omit(user, ["password"]) + ); } }); it("should fail when getting one by an unknown id", async () => { - const id = Math.max(...dbTest.db.users.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + const id = + Math.max(...dbTest.db.users.map(({ _id }) => _id)) + 1; + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); describe("Find many", () => { @@ -78,7 +86,10 @@ describe("UserService", () => { const loop = Math.min(5, users.length); for (let skip = 0; skip < loop; ++skip) { - const { pagination } = await service.findAndCount({}, { limit: 1, skip }); + const { pagination } = await service.findAndCount( + {}, + { limit: 1, skip } + ); expect(pagination.range.start).toBe(skip); expect(pagination.range.end).toBe(skip + 1); @@ -114,7 +125,9 @@ describe("UserService", () => { // Update an entity and check its content const [user] = dbTest.db.users; - const toUpdate: UserUpdateEntity = { email: `${user.email}-${user.email}` }; + const toUpdate: UserUpdateEntity = { + email: `${user.email}-${user.email}` + }; const updated = await service.update(user._id, toUpdate); expect(updated.email).toBe(toUpdate.email); @@ -132,16 +145,18 @@ describe("UserService", () => { const [, user] = dbTest.db.users; const toUpdate: UserUpdateEntity = { email: user.email }; const updated = await service.update(user._id, toUpdate); - expect(updated.toJSON()).toStrictEqual(omit(user, ["password"])); + expect(updated.toJSON()).toStrictEqual( + omit(user, ["password"]) + ); }); it("should fail when a uniqueness constraint is not respected", async () => { const [user1, user2] = dbTest.db.users; const toUpdate: UserUpdateEntity = { email: user1.email }; - await expect(service.update(user2._id, toUpdate)).rejects.toThrow( - UniqueConstraintViolationException - ); + await expect( + service.update(user2._id, toUpdate) + ).rejects.toThrow(UniqueConstraintViolationException); }); }); @@ -153,16 +168,21 @@ describe("UserService", () => { // Delete an entity const [user] = dbTest.db.users; const deleted = await service.delete(user._id); - expect(deleted.toJSON!()).toStrictEqual(omit(user, ["password"])); + expect(deleted.toJSON!()).toStrictEqual( + omit(user, ["password"]) + ); // Check that the entity is really deleted const { data: after } = await service.findAndCount(); expect(after).toHaveLength(before.length - 1); - expect(after.some(({ _id }) => _id === deleted._id)).toBeFalse(); + expect(after.some(({ _id }) => _id === deleted._id)).toBe( + false + ); }); it("should not delete an unknown id", async () => { - const id = Math.max(...dbTest.db.users.map(({ _id }) => _id)) + 1; + const id = + Math.max(...dbTest.db.users.map(({ _id }) => _id)) + 1; await expect(service.delete(id)).rejects.toThrow(NotFoundError); }); }); diff --git a/apps/backend/src/app/user/user.service.ts b/apps/backend/src/app/user/user.service.ts index 63b6cc3e..80494321 100644 --- a/apps/backend/src/app/user/user.service.ts +++ b/apps/backend/src/app/user/user.service.ts @@ -1,5 +1,9 @@ import { Injectable } from "@nestjs/common"; -import { UserCreateDto, UserDto, UserUpdateDto } from "~/lib/common/app/user/dtos"; +import { + UserCreateDto, + UserDto, + UserUpdateDto +} from "~/lib/common/app/user/dtos"; import { UserEntity } from "./user.entity"; import { UserRepository } from "./user.repository"; @@ -8,13 +12,19 @@ import { EntityService } from "../_lib/entity"; /** * Update of an user from the service */ -export interface UserUpdateEntity extends UserUpdateDto, Pick, "email"> {} +export interface UserUpdateEntity + extends UserUpdateDto, + Pick, "email"> {} /** * Service to manages [users]{@link UserEntity}. */ @Injectable() -export class UserService extends EntityService { +export class UserService extends EntityService< + UserEntity, + UserCreateDto, + UserUpdateEntity +> { /** * Constructor with "dependency injection" * @@ -31,6 +41,9 @@ export class UserService extends EntityService { @ApiOkResponse({ type: GraphDto }) @Get(`/:id${WORKFLOW_LOOK_FOR_GRAPH_ENDPOINT}`) public lookForGraph(@Param("id") id: number) { - return this.findById(id).then(({ __graph }) => this.graphService.findById(__graph)); + return this.findById(id).then(({ __graph }) => + this.graphService.findById(__graph) + ); } } diff --git a/apps/backend/src/app/workflow/workflow.entity.ts b/apps/backend/src/app/workflow/workflow.entity.ts index 052b557c..c4bd90a8 100644 --- a/apps/backend/src/app/workflow/workflow.entity.ts +++ b/apps/backend/src/app/workflow/workflow.entity.ts @@ -28,7 +28,10 @@ const GraphProperty = ({ foreign }: Pick) => * The entity class to manage workflows */ @Entity({ customRepository: () => WorkflowRepository }) -export class WorkflowEntity extends EntityBase implements DtoToEntity { +export class WorkflowEntity + extends EntityBase + implements DtoToEntity +{ /** @inheritDoc */ @GraphProperty({ foreign: false }) public readonly __graph!: number; diff --git a/apps/backend/src/app/workflow/workflow.executor.spec.ts b/apps/backend/src/app/workflow/workflow.executor.spec.ts index 417d6808..82039de1 100644 --- a/apps/backend/src/app/workflow/workflow.executor.spec.ts +++ b/apps/backend/src/app/workflow/workflow.executor.spec.ts @@ -82,8 +82,10 @@ describe("WorkflowExecutor", () => { __to: nodeCodeInput._id }); - const beforeExecutionTime = new Date().getTime(); - const state$ = await service.findById(_id).then(w => executor.execute(w)); + const beforeExecutionTime = Date.now(); + const state$ = await service + .findById(_id) + .then(w => executor.execute(w)); const trace: Record = { "propagation-enter": [], "propagation-leave": [], @@ -107,7 +109,9 @@ describe("WorkflowExecutor", () => { expect(trace["resolution-end"]).toHaveLength(2); const expected = [nodeTrigger._id, nodeCode._id].sort(); - expect(trace["resolution-start"].slice().sort()).toStrictEqual(expected); + expect(trace["resolution-start"].slice().sort()).toStrictEqual( + expected + ); expect(trace["resolution-end"].slice().sort()).toStrictEqual(expected); // --- Test executed value diff --git a/apps/backend/src/app/workflow/workflow.executor.ts b/apps/backend/src/app/workflow/workflow.executor.ts index 90e56a01..fc5ba861 100644 --- a/apps/backend/src/app/workflow/workflow.executor.ts +++ b/apps/backend/src/app/workflow/workflow.executor.ts @@ -32,7 +32,10 @@ export class WorkflowExecutor { */ public async execute(workflow: WorkflowEntity) { const { node } = await this.service.findTrigger(workflow); - return this.graphExecutor.execute({ graphId: workflow.__graph, startAt: [node._id] }); + return this.graphExecutor.execute({ + graphId: workflow.__graph, + startAt: [node._id] + }); } /** diff --git a/apps/backend/src/app/workflow/workflow.repository.ts b/apps/backend/src/app/workflow/workflow.repository.ts index 66e723c5..e91bba09 100644 --- a/apps/backend/src/app/workflow/workflow.repository.ts +++ b/apps/backend/src/app/workflow/workflow.repository.ts @@ -1,4 +1,8 @@ -import { CreateOptions, EntityRepository, RequiredEntityData } from "@mikro-orm/core"; +import { + CreateOptions, + EntityRepository, + RequiredEntityData +} from "@mikro-orm/core"; import { WorkflowEntity } from "./workflow.entity"; import { GraphEntity } from "../graph/graph.entity"; @@ -19,12 +23,18 @@ export class WorkflowRepository extends EntityRepository { if (data.__graph) { // The graph property is the persisted entity // -> create an empty entity with primary key (used for seeders) - return super.create({ ...data, graph: { _id: data.__graph } }, options); + return super.create( + { ...data, graph: { _id: data.__graph } }, + options + ); } // On creation -> use an empty entity to automatically link it - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast to satisfy TS - const graph = this.getEntityManager().create(GraphEntity, {} as GraphEntity); + + const graph = this.getEntityManager().create( + GraphEntity, + {} as unknown as never + ); return super.create({ ...data, graph }, options); } } diff --git a/apps/backend/src/app/workflow/workflow.scheduler.spec.ts b/apps/backend/src/app/workflow/workflow.scheduler.spec.ts index 81eb3e6d..1dea7c11 100644 --- a/apps/backend/src/app/workflow/workflow.scheduler.spec.ts +++ b/apps/backend/src/app/workflow/workflow.scheduler.spec.ts @@ -49,7 +49,9 @@ describe("WorkflowScheduler", () => { const getCode = () => { const { db } = getConfiguration(); const rawContent = fs - .readFileSync(path.resolve("libs/common/src/seeds/codes/create-graph.js")) + .readFileSync( + path.resolve("libs/common/src/seeds/codes/create-graph.js") + ) .toString(); const value = 1000 + Math.floor(10e5 * Math.random()); @@ -119,7 +121,7 @@ describe("WorkflowScheduler", () => { // ----- Tests await scheduler.register(workflow); - expect(scheduler.isRegistered(workflow)).toBeTrue(); + expect(scheduler.isRegistered(workflow)).toBe(true); // The workflow could not be called when sleeping for 1 seconds or less // And it could be executed twice for more time @@ -141,16 +143,21 @@ describe("WorkflowScheduler", () => { } } - throw new Error(`No new graph detected from the workflow in less than ${timeMax}ms.`); + throw new Error( + `No new graph detected from the workflow in less than ${timeMax}ms.` + ); })(); await scheduler.unregister(workflow); - expect(scheduler.isRegistered(workflow)).toBeFalse(); + expect(scheduler.isRegistered(workflow)).toBe(false); const { data: [graphTest], pagination: { total: totalAfter } - } = await graphService.findAndCount({}, { limit: 1, order: [{ _id: "desc" }] }); + } = await graphService.findAndCount( + {}, + { limit: 1, order: [{ _id: "desc" }] } + ); expect(totalAfter).toBe(totalBefore + 1); expect(graphTest._id).toBe(value); @@ -159,12 +166,12 @@ describe("WorkflowScheduler", () => { it("should register and unregister by (de)activating a workflow", async () => { const { workflow } = await seed(CronExpression.EVERY_YEAR); - expect(scheduler.isRegistered(workflow)).toBeFalse(); + expect(scheduler.isRegistered(workflow)).toBe(false); await service.update(workflow._id, { active: true }); - expect(scheduler.isRegistered(workflow)).toBeTrue(); + expect(scheduler.isRegistered(workflow)).toBe(true); await service.update(workflow._id, { active: false }); - expect(scheduler.isRegistered(workflow)).toBeFalse(); + expect(scheduler.isRegistered(workflow)).toBe(false); }); }); diff --git a/apps/backend/src/app/workflow/workflow.scheduler.ts b/apps/backend/src/app/workflow/workflow.scheduler.ts index 8f6716e9..23bac58f 100644 --- a/apps/backend/src/app/workflow/workflow.scheduler.ts +++ b/apps/backend/src/app/workflow/workflow.scheduler.ts @@ -37,7 +37,9 @@ export class WorkflowScheduler { * @returns if the workflow is already registered */ public isRegistered(workflow: WorkflowEntity) { - return this.scheduler.getCronJobs().has(this.getCronJobName(workflow._id)); + return this.scheduler + .getCronJobs() + .has(this.getCronJobName(workflow._id)); } /** diff --git a/apps/backend/src/app/workflow/workflow.service.spec.ts b/apps/backend/src/app/workflow/workflow.service.spec.ts index 59c1841f..c925a84e 100644 --- a/apps/backend/src/app/workflow/workflow.service.spec.ts +++ b/apps/backend/src/app/workflow/workflow.service.spec.ts @@ -1,6 +1,12 @@ -import { NotFoundError, UniqueConstraintViolationException } from "@mikro-orm/core"; +import { + NotFoundError, + UniqueConstraintViolationException +} from "@mikro-orm/core"; import { Test, TestingModule } from "@nestjs/testing"; -import { WorkflowCreateDto, WorkflowUpdateDto } from "~/lib/common/app/workflow/dtos"; +import { + WorkflowCreateDto, + WorkflowUpdateDto +} from "~/lib/common/app/workflow/dtos"; import { BASE_SEED } from "~/lib/common/seeds"; import { WorkflowNoTriggerException } from "./exceptions"; @@ -39,7 +45,9 @@ describe("WorkflowService", () => { it("should activate a workflow", async () => { const [{ _id }] = db.workflows; - await expect(service.update(_id, { active: true })).resolves.toBeDefined(); + await expect( + service.update(_id, { active: true }) + ).resolves.toBeDefined(); }); it("should not activate a workflow where there is no trigger", async () => { @@ -55,11 +63,14 @@ describe("WorkflowService", () => { it("should have a graph linked when a workflow is created", async () => { const workflow = await service.create({ name: "A new workflow" }); - expect(workflow.__graph).toBeNumber(); + expect(typeof workflow.__graph === "number").toBe(true); const { pagination: { total } - } = await graphService.findAndCount({ _id: workflow.__graph }, { limit: 0 }); + } = await graphService.findAndCount( + { _id: workflow.__graph }, + { limit: 0 } + ); expect(total).toBe(1); }); @@ -68,14 +79,20 @@ describe("WorkflowService", () => { const { pagination: { total: before } - } = await graphService.findAndCount({ _id: workflow.__graph }, { limit: 0 }); + } = await graphService.findAndCount( + { _id: workflow.__graph }, + { limit: 0 } + ); expect(before).toBe(1); await service.delete(workflow._id); const { pagination: { total: after } - } = await graphService.findAndCount({ _id: workflow.__graph }, { limit: 0 }); + } = await graphService.findAndCount( + { _id: workflow.__graph }, + { limit: 0 } + ); expect(after).toBe(0); }); }); @@ -93,7 +110,9 @@ describe("WorkflowService", () => { it("should fail when getting one by an unknown id", async () => { const id = Math.max(...db.workflows.map(({ _id }) => _id)) + 1; - await expect(service.findById(id)).rejects.toThrow(NotFoundError); + await expect(service.findById(id)).rejects.toThrow( + NotFoundError + ); }); describe("Find many", () => { @@ -141,7 +160,9 @@ describe("WorkflowService", () => { }); it("should fail when a uniqueness constraint is not respected", async () => { - const toCreate: WorkflowCreateDto = { name: db.workflows[0].name }; + const toCreate: WorkflowCreateDto = { + name: db.workflows[0].name + }; await expect(service.create(toCreate)).rejects.toThrow( UniqueConstraintViolationException ); @@ -157,7 +178,9 @@ describe("WorkflowService", () => { // Update an entity and check its content const [workflow] = db.workflows; - const toUpdate: WorkflowUpdateDto = { name: `${workflow.name}-${workflow.name}` }; + const toUpdate: WorkflowUpdateDto = { + name: `${workflow.name}-${workflow.name}` + }; const updated = await service.update(workflow._id, toUpdate); expect(updated.name).toBe(toUpdate.name); @@ -175,9 +198,9 @@ describe("WorkflowService", () => { const [workflow1, workflow2] = db.workflows; const toUpdate: WorkflowUpdateDto = { name: workflow1.name }; - await expect(service.update(workflow2._id, toUpdate)).rejects.toThrow( - UniqueConstraintViolationException - ); + await expect( + service.update(workflow2._id, toUpdate) + ).rejects.toThrow(UniqueConstraintViolationException); }); }); @@ -196,7 +219,9 @@ describe("WorkflowService", () => { // Check that the entity is really deleted const { data: after } = await service.findAndCount(); expect(after).toHaveLength(before.length - 1); - expect(after.some(({ _id }) => _id === deleted._id)).toBeFalse(); + expect(after.some(({ _id }) => _id === deleted._id)).toBe( + false + ); }); it("should not delete an unknown id", async () => { diff --git a/apps/backend/src/app/workflow/workflow.service.ts b/apps/backend/src/app/workflow/workflow.service.ts index f04dee68..1ba2577d 100644 --- a/apps/backend/src/app/workflow/workflow.service.ts +++ b/apps/backend/src/app/workflow/workflow.service.ts @@ -1,8 +1,22 @@ -import { CreateRequestContext, EventArgs, EventSubscriber, MikroORM } from "@mikro-orm/core"; -import { forwardRef, Inject, Injectable, NotFoundException, OnModuleInit } from "@nestjs/common"; +import { + CreateRequestContext, + EventArgs, + EventSubscriber, + MikroORM +} from "@mikro-orm/core"; +import { + forwardRef, + Inject, + Injectable, + NotFoundException, + OnModuleInit +} from "@nestjs/common"; import { NodeBehaviorType } from "~/lib/common/app/node/dtos/behaviors/node-behavior.type"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; -import { WorkflowCreateDto, WorkflowUpdateDto } from "~/lib/common/app/workflow/dtos"; +import { + WorkflowCreateDto, + WorkflowUpdateDto +} from "~/lib/common/app/workflow/dtos"; import { EntityId } from "~/lib/common/dtos/entity"; import { WorkflowNoTriggerException } from "./exceptions"; @@ -42,7 +56,10 @@ export class WorkflowService ) { super(repository); - repository.getEntityManager().getEventManager().registerSubscriber(this); + repository + .getEntityManager() + .getEventManager() + .registerSubscriber(this); } /** @inheritDoc */ @@ -90,17 +107,21 @@ export class WorkflowService public async onModuleInit() { const { data: workflows } = await this.findAndCount({ active: true }); - await Promise.all(workflows.map(workflow => this.workflowScheduler.register(workflow))); + await Promise.all( + workflows.map(workflow => this.workflowScheduler.register(workflow)) + ); } /** @inheritDoc */ public override delete(id: EntityId): Promise { - return this.findById(id, { populate: { graph: true } }).then(async entity => { - // Cascade integrity -> deleting the graph deletes the workflow - // TODO: Reverse the relation ? Remove the cascade and delete manually - await this.graphService._deleteFromParent(entity.graph); - return entity; - }); + return this.findById(id, { populate: { graph: true } }).then( + async entity => { + // Cascade integrity -> deleting the graph deletes the workflow + // TODO: Reverse the relation ? Remove the cascade and delete manually + await this.graphService._deleteFromParent(entity.graph); + return entity; + } + ); } /** @@ -132,7 +153,9 @@ export class WorkflowService ); if (data.length !== 1) { - throw new NotFoundException(`No trigger found for the workflow ${_id}`); + throw new NotFoundException( + `No trigger found for the workflow ${_id}` + ); } const [node] = data; diff --git a/apps/backend/src/config.e2e.ts b/apps/backend/src/config.e2e.ts index fa1ff48c..4ad46006 100644 --- a/apps/backend/src/config.e2e.ts +++ b/apps/backend/src/config.e2e.ts @@ -21,7 +21,7 @@ export const config = { }, host: { // The e2e instance can be used in frontend:e2e:watch mode - cors: { origin: /\/\/localhost(:[0-9]{1,5})+/ }, + cors: { origin: /\/\/localhost(:\d{1,5})+/ }, globalPrefix: "/e2e/api", name: "127.0.0.1", port: 32300 diff --git a/apps/backend/src/configuration/config.default.ts b/apps/backend/src/configuration/config.default.ts index 8d93abe1..7d6f8f2e 100644 --- a/apps/backend/src/configuration/config.default.ts +++ b/apps/backend/src/configuration/config.default.ts @@ -19,9 +19,9 @@ export const configDefault: Configuration = { host: { cors: { origin: [ - /\/\/127.0.0.[0-9]{1,3}/, - /\/\/192.168.[0-9]{1,3}.[0-9]{1,3}/, - /\/\/localhost(:[0-9]{1,5})+/ + /\/\/127.0.0.\d{1,3}/, + /\/{2}192.168(?:.\d{1,3}){2}/, + /\/\/localhost(:\d{1,5})+/ ] }, globalPrefix: "api", diff --git a/apps/backend/src/configuration/configuration.ts b/apps/backend/src/configuration/configuration.ts index 545120b7..540bdf05 100644 --- a/apps/backend/src/configuration/configuration.ts +++ b/apps/backend/src/configuration/configuration.ts @@ -22,7 +22,9 @@ export function _setConfiguration(config: ConfigurationPartial) { throw new Error("Configuration already set"); } - return (_conf = Object.freeze(deepmerge(configDefault, config) as ReadonlyDeep)); + return (_conf = Object.freeze( + deepmerge(configDefault, config) as ReadonlyDeep + )); } /** diff --git a/apps/backend/src/health/health.service.ts b/apps/backend/src/health/health.service.ts index 15693a2f..56660074 100644 --- a/apps/backend/src/health/health.service.ts +++ b/apps/backend/src/health/health.service.ts @@ -49,7 +49,11 @@ export class HealthService { public check(): Promise { return this.heath.check([ () => this.db.pingCheck("database"), - () => this.memory.checkHeap("memory_heap", this.THRESHOLDS.MEMORY.HEAP), + () => + this.memory.checkHeap( + "memory_heap", + this.THRESHOLDS.MEMORY.HEAP + ), () => this.memory.checkRSS("memory_rss", this.THRESHOLDS.MEMORY.RSS) ]); } diff --git a/apps/backend/src/main.e2e.ts b/apps/backend/src/main.e2e.ts index 220946e1..0f663674 100644 --- a/apps/backend/src/main.e2e.ts +++ b/apps/backend/src/main.e2e.ts @@ -4,7 +4,10 @@ import { HttpServer } from "@nestjs/common/interfaces/http/http-server.interface import { Request, Response } from "express"; import { bootstrap } from "./bootstrap"; -import { E2E_ENDPOINT_DB_SEEDING, E2eEndpointDbSeedingBody } from "./config.e2e"; +import { + E2E_ENDPOINT_DB_SEEDING, + E2eEndpointDbSeedingBody +} from "./config.e2e"; import { getConfiguration } from "./configuration"; import { DbTestHelper, isDbTestSampleValid } from "../test/db-test"; @@ -14,7 +17,10 @@ bootstrap() .then(async app => { const { host } = getConfiguration(); - const httpAdapter = app.getHttpAdapter() as HttpServer; + const httpAdapter = app.getHttpAdapter() as HttpServer< + Request, + Response + >; const orm = app.get(MikroORM); httpAdapter.get(E2E_ENDPOINT_DB_SEEDING, (req, res) => { @@ -23,7 +29,9 @@ bootstrap() res.json({ ok: false }); }; - const { sample } = req.query as Partial>; + const { sample } = req.query as Partial< + Record + >; if (!sample || !isDbTestSampleValid(sample)) { sendKo(400); @@ -42,7 +50,11 @@ bootstrap() new DbTestHelper(app, { sample: "base" }).refresh() ); await app.listen(host.port, host.name); - Logger.log(`🚀 Application is running on: ${await app.getUrl()}/${host.globalPrefix}`); + Logger.log( + `🚀 Application is running on: ${await app.getUrl()}/${ + host.globalPrefix + }` + ); }) .catch((error: unknown) => { // eslint-disable-next-line no-console -- bootstrap of the application diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index d7474760..2cf8111b 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -8,7 +8,11 @@ bootstrap() const { host } = getConfiguration(); await app.listen(host.port, host.name); - Logger.log(`🚀 Application is running on: ${await app.getUrl()}/${host.globalPrefix}`); + Logger.log( + `🚀 Application is running on: ${await app.getUrl()}/${ + host.globalPrefix + }` + ); }) .catch((error: unknown) => { // eslint-disable-next-line no-console -- bootstrap of the application diff --git a/apps/backend/src/orm.config.ts b/apps/backend/src/orm.config.ts index 7159383e..fcf9cb07 100644 --- a/apps/backend/src/orm.config.ts +++ b/apps/backend/src/orm.config.ts @@ -30,7 +30,9 @@ class NamingStrategy extends UnderscoreNamingStrategy { const tableName = super.classToTableName(entityName); const suffix = "_entity"; - return tableName.endsWith(suffix) ? tableName.slice(0, -suffix.length) : tableName; + return tableName.endsWith(suffix) + ? tableName.slice(0, -suffix.length) + : tableName; } } @@ -49,7 +51,13 @@ const ormConfig = defineConfig({ // For app code discovery: { disableDynamicFileAccess: true }, - entities: [CategoryEntity, ...GRAPH_ENTITIES, ...NODE_ENTITIES, UserEntity, WorkflowEntity], + entities: [ + CategoryEntity, + ...GRAPH_ENTITIES, + ...NODE_ENTITIES, + UserEntity, + WorkflowEntity + ], forceUndefined: false, metadataProvider: TsMorphMetadataProvider, namingStrategy: NamingStrategy, @@ -69,7 +77,9 @@ const ormConfig = defineConfig({ seeder: { emit: "ts", fileName: className => - `${kebabCase(className).slice(0, -suffixes.seeder.length - 1)}.${suffixes.seeder}`, + `${kebabCase(className).slice(0, -suffixes.seeder.length - 1)}.${ + suffixes.seeder + }`, pathTs: path.join(ormPath, "seeders") } }); diff --git a/apps/backend/src/orm/filters/foreign-key.constraint.filter.ts b/apps/backend/src/orm/filters/foreign-key.constraint.filter.ts index 0551cc6d..4b4fd283 100644 --- a/apps/backend/src/orm/filters/foreign-key.constraint.filter.ts +++ b/apps/backend/src/orm/filters/foreign-key.constraint.filter.ts @@ -9,12 +9,19 @@ import { BaseExceptionFilter } from "@nestjs/core"; @Catch(ForeignKeyConstraintViolationException) export class ForeignKeyConstraintFilter extends BaseExceptionFilter { /** @inheritDoc */ - public override catch(exception: ForeignKeyConstraintViolationException, host: ArgumentsHost) { + public override catch( + exception: ForeignKeyConstraintViolationException, + host: ArgumentsHost + ) { super.catch( - new NotFoundException("A foreign relation key did not match an entity", { - cause: exception, - description: (exception as unknown as { detail: string }).detail - }), + new NotFoundException( + "A foreign relation key did not match an entity", + { + cause: exception, + description: (exception as unknown as { detail: string }) + .detail + } + ), host ); } diff --git a/apps/backend/src/orm/filters/unique.constraint.filter.ts b/apps/backend/src/orm/filters/unique.constraint.filter.ts index f104ab29..735d2f95 100644 --- a/apps/backend/src/orm/filters/unique.constraint.filter.ts +++ b/apps/backend/src/orm/filters/unique.constraint.filter.ts @@ -9,12 +9,19 @@ import { BaseExceptionFilter } from "@nestjs/core"; @Catch(UniqueConstraintViolationException) export class UniqueConstraintFilter extends BaseExceptionFilter { /** @inheritDoc */ - public override catch(exception: UniqueConstraintViolationException, host: ArgumentsHost) { + public override catch( + exception: UniqueConstraintViolationException, + host: ArgumentsHost + ) { super.catch( - new ConflictException("A uniqueness constraint has not been respected.", { - cause: exception, - description: (exception as unknown as { detail: string }).detail - }), + new ConflictException( + "A uniqueness constraint has not been respected.", + { + cause: exception, + description: (exception as unknown as { detail: string }) + .detail + } + ), host ); } diff --git a/apps/backend/src/orm/orm.module.ts b/apps/backend/src/orm/orm.module.ts index ff964988..34e46374 100644 --- a/apps/backend/src/orm/orm.module.ts +++ b/apps/backend/src/orm/orm.module.ts @@ -2,7 +2,11 @@ import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; import { APP_FILTER } from "@nestjs/core"; -import { ForeignKeyConstraintFilter, NotFoundFilter, UniqueConstraintFilter } from "./filters"; +import { + ForeignKeyConstraintFilter, + NotFoundFilter, + UniqueConstraintFilter +} from "./filters"; import ormConfig from "../orm.config"; @Module({ diff --git a/apps/backend/src/orm/seeders/_lib/mocked-db.seeder.ts b/apps/backend/src/orm/seeders/_lib/mocked-db.seeder.ts index c84df2e8..8b53f65d 100644 --- a/apps/backend/src/orm/seeders/_lib/mocked-db.seeder.ts +++ b/apps/backend/src/orm/seeders/_lib/mocked-db.seeder.ts @@ -23,7 +23,8 @@ export abstract class MockedDbSeeder extends Seeder { * @returns The mocked DB of this Seeder */ public static GetMockedDb() { - const db = new (this.prototype.constructor as new () => MockedDbSeeder)().db; + const db = new (this.prototype + .constructor as new () => MockedDbSeeder)().db; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Could happen if (!db) { @@ -49,7 +50,9 @@ export abstract class MockedDbSeeder extends Seeder { // FIXME: Remove this (with another order or inserts ?) // Disable FK checks - await em.getConnection().execute("SET session_replication_role = 'replica';"); + await em + .getConnection() + .execute("SET session_replication_role = 'replica';"); const { categories, @@ -96,13 +99,19 @@ export abstract class MockedDbSeeder extends Seeder { await em.flush(); // Enable FK checks - await em.getConnection().execute("SET session_replication_role = 'origin';"); + await em + .getConnection() + .execute("SET session_replication_role = 'origin';"); for (const { __categories, _id } of nodes) { - const node = await em.findOneOrFail(NodeEntity, _id, { populate: ["categories"] }); + const node = await em.findOneOrFail(NodeEntity, _id, { + populate: ["categories"] + }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Does exist with the `populate` option node.categories!.add( - __categories.map(id => Reference.createFromPK(CategoryEntity, id)) + __categories.map(id => + Reference.createFromPK(CategoryEntity, id) + ) ); } @@ -113,7 +122,9 @@ export abstract class MockedDbSeeder extends Seeder { // Need to update the sequence when entities are added manually const primaryKey: keyof EntityBase = "_id"; // // TODO: better (if the table name is set manually) - const tblName = em.config.getNamingStrategy().classToTableName(entity.name); + const tblName = em.config + .getNamingStrategy() + .classToTableName(entity.name); return em .getConnection() .execute( diff --git a/apps/backend/test/db-test/db-test.helper.ts b/apps/backend/test/db-test/db-test.helper.ts index b21289ad..ae9886e7 100644 --- a/apps/backend/test/db-test/db-test.helper.ts +++ b/apps/backend/test/db-test/db-test.helper.ts @@ -52,7 +52,9 @@ export class DbTestHelper { break; default: - throw new Error(`No sample found for ${params?.sample ?? "base"}`); + throw new Error( + `No sample found for ${params?.sample ?? "base"}` + ); } this.orm = module.get(MikroORM); @@ -85,7 +87,9 @@ export class DbTestHelper { public async refresh() { this.orm.em.clear(); await this.orm.getSchemaGenerator().refreshDatabase(); - await this.orm.getSeeder().seed(this.seeder as typeof MockedDbSeeder & (new () => Seeder)); + await this.orm + .getSeeder() + .seed(this.seeder as typeof MockedDbSeeder & (new () => Seeder)); this.orm.em.clear(); } } diff --git a/apps/backend/test/support/global/logger-test.ts b/apps/backend/test/support/global/logger-test.ts index 9f06c9c5..b3705382 100644 --- a/apps/backend/test/support/global/logger-test.ts +++ b/apps/backend/test/support/global/logger-test.ts @@ -63,5 +63,7 @@ class Dummy implements LoggerTest { } } -export const LoggerTest: (dummy?: boolean) => (prefix: string) => LoggerTest = dummy => +export const LoggerTest: ( + dummy?: boolean +) => (prefix: string) => LoggerTest = dummy => dummy ? () => new Dummy() : prefix => new Logger(prefix); diff --git a/apps/backend/test/support/global/setup.ts b/apps/backend/test/support/global/setup.ts index ce9abc46..b2f73498 100644 --- a/apps/backend/test/support/global/setup.ts +++ b/apps/backend/test/support/global/setup.ts @@ -7,7 +7,7 @@ import { GlobalThis } from "../global-this.type"; export async function globalSetup(logger: LoggerTest) { const docker = new Dockerode(); - const imageTag = "postgres:15-alpine"; + const imageTag = "postgres:16-alpine"; const { name, password, port, username } = configE2e.db; const existing = await docker.listContainers().then(containers => { @@ -32,7 +32,9 @@ export async function globalSetup(logger: LoggerTest) { ); logger.log(` It will use the existing one`); - (globalThis as unknown as GlobalThis).jest_config = { container: "existing" }; + (globalThis as unknown as GlobalThis).jest_config = { + container: "existing" + }; return; } @@ -54,7 +56,11 @@ export async function globalSetup(logger: LoggerTest) { logger.log(`Creating container`); const container = await docker.createContainer({ - Env: [`POSTGRES_DB=${name}`, `POSTGRES_USER=${username}`, `POSTGRES_PASSWORD=${password}`], + Env: [ + `POSTGRES_DB=${name}`, + `POSTGRES_USER=${username}`, + `POSTGRES_PASSWORD=${password}` + ], HostConfig: { PortBindings: { "5432/tcp": [ @@ -75,7 +81,9 @@ export async function globalSetup(logger: LoggerTest) { const timeout = 5000; const timeoutId = setTimeout(() => { reject( - new Error(`Did not succeed to determine if psql is ready in less than ${timeout}ms`) + new Error( + `Did not succeed to determine if psql is ready in less than ${timeout}ms` + ) ); }, timeout); @@ -106,7 +114,9 @@ export async function globalSetup(logger: LoggerTest) { export default async function (config: Config) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- One or the other - const logger = LoggerTest(config.watch || config.watchAll)("[global-setup]"); + const logger = LoggerTest(config.watch || config.watchAll)( + "[global-setup]" + ); logger.emptyLine(); await globalSetup(logger); diff --git a/apps/backend/test/support/global/teardown.ts b/apps/backend/test/support/global/teardown.ts index 5d5a3c54..bf0804e4 100644 --- a/apps/backend/test/support/global/teardown.ts +++ b/apps/backend/test/support/global/teardown.ts @@ -25,7 +25,9 @@ export async function globalTeardown(logger: LoggerTest) { export default async function (config: Config) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- One or the other - const logger = LoggerTest(config.watch || config.watchAll)("[global-teardown]"); + const logger = LoggerTest(config.watch || config.watchAll)( + "[global-teardown]" + ); logger.emptyLine(); await globalTeardown(logger); diff --git a/apps/backend/tsconfig.app.json b/apps/backend/tsconfig.app.json index 6716a8a4..2c6d2d37 100644 --- a/apps/backend/tsconfig.app.json +++ b/apps/backend/tsconfig.app.json @@ -7,7 +7,12 @@ "outDir": "../../dist/out-tsc", "target": "es2021" }, - "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts", "src/orm/seeders"], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/orm/seeders" + ], "extends": "./tsconfig.json", "include": ["src/**/*.ts"] } diff --git a/apps/backend/tsconfig.doc.json b/apps/backend/tsconfig.doc.json index 0b5f8e7a..b2f94207 100644 --- a/apps/backend/tsconfig.doc.json +++ b/apps/backend/tsconfig.doc.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.app.json", - "include": ["src/**/*.ts", "../../libs/common/src/**/*.ts"] + "include": ["../../libs/common/src/**/*.ts", "src/**/*.ts"] } diff --git a/apps/backend/tsconfig.spec.json b/apps/backend/tsconfig.spec.json index 83ff54a5..5323318a 100644 --- a/apps/backend/tsconfig.spec.json +++ b/apps/backend/tsconfig.spec.json @@ -5,5 +5,10 @@ "types": ["jest", "jest-extended", "node"] }, "extends": "./tsconfig.json", - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "jest.config.ts", + "src/**/*.d.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] } diff --git a/apps/backend/webpack.config.ts b/apps/backend/webpack.config.ts index e066c6e4..c57dc4f6 100644 --- a/apps/backend/webpack.config.ts +++ b/apps/backend/webpack.config.ts @@ -26,11 +26,16 @@ export default composePlugins(withNx(), config => { context && request && request.startsWith(moduleToAlias) && - context.startsWith(path.resolve(__dirname, "../..", "node_modules")) + context.startsWith( + path.resolve(__dirname, "../..", "node_modules") + ) ) { // When the module is used from another `node_module`, // load it normally (as external: https://webpack.js.org/configuration/externals/) - callback(undefined, `${dependencyType ?? "commonjs"} ${request}`); + callback( + undefined, + `${dependencyType ?? "commonjs"} ${request}` + ); return; } diff --git a/apps/frontend-e2e/.eslintrc.json b/apps/frontend-e2e/.eslintrc.json index ca90e914..0b2531a5 100644 --- a/apps/frontend-e2e/.eslintrc.json +++ b/apps/frontend-e2e/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "extends": ["../../.eslintrc.json", "plugin:cypress/recommended"], "overrides": [ { "files": ["*.ts"], diff --git a/apps/frontend-e2e/cypress.config.ts b/apps/frontend-e2e/cypress.config.ts index fa3dc508..3d497133 100644 --- a/apps/frontend-e2e/cypress.config.ts +++ b/apps/frontend-e2e/cypress.config.ts @@ -8,25 +8,32 @@ const e2eNodeEvents: Cypress.ResolvedConfigOptions["setupNodeEvents"] = on => { let initialized = false; const setup = () => - globalSetup({ prefix: "frontend-e2e-setup" }).then(() => (initialized = true)); + globalSetup({ prefix: "frontend-e2e-setup" }).then( + () => (initialized = true) + ); const teardown = () => - globalTeardown({ prefix: "frontend-e2e-teardown" }).then(() => (initialized = false)); + globalTeardown({ prefix: "frontend-e2e-teardown" }).then( + () => (initialized = false) + ); // Taken from https://docs.cypress.io/guides/guides/screenshots-and-videos#Delete-videos-for-specs-without-failing-or-retried-tests - on("after:spec", (spec: Cypress.Spec, results: CypressCommandLine.RunResult) => { - if (!results || !results.video) { - return; - } + on( + "after:spec", + (spec: Cypress.Spec, results: CypressCommandLine.RunResult) => { + if (!results || !results.video) { + return; + } - // Do we have failures for any retry attempts? - const failures = results.tests.some(test => - test.attempts.some(attempt => attempt.state === "failed") - ); - if (!failures) { - // delete the video if the spec passed and no tests retried - fs.unlinkSync(results.video); + // Do we have failures for any retry attempts? + const failures = results.tests.some(test => + test.attempts.some(attempt => attempt.state === "failed") + ); + if (!failures) { + // delete the video if the spec passed and no tests retried + fs.unlinkSync(results.video); + } } - }); + ); // Backend initialisation diff --git a/apps/frontend-e2e/project.json b/apps/frontend-e2e/project.json index ee729e4b..750449ca 100644 --- a/apps/frontend-e2e/project.json +++ b/apps/frontend-e2e/project.json @@ -17,7 +17,9 @@ "lint": { "executor": "@nx/linter:eslint", "options": { - "lintFilePatterns": ["apps/frontend-e2e/**/*.{ts,js,html,md,json,yml}"] + "lintFilePatterns": [ + "apps/frontend-e2e/**/*.{ts,js,html,md,json,yml}" + ] }, "outputs": ["{options.outputFile}"] } diff --git a/apps/frontend-e2e/src/e2e/auth/login.cy.ts b/apps/frontend-e2e/src/e2e/auth/login.cy.ts index 33481594..e76ebd25 100644 --- a/apps/frontend-e2e/src/e2e/auth/login.cy.ts +++ b/apps/frontend-e2e/src/e2e/auth/login.cy.ts @@ -48,7 +48,10 @@ describe("Auth", () => { cy.get("#mat-input-1").type(password); cy.get(".mdc-button").click(); - cy.get(".mat-mdc-card-title").should("contain.text", `Hello ${email}!`); + cy.get(".mat-mdc-card-title").should( + "contain.text", + `Hello ${email}!` + ); // /* ==== End Cypress Studio ==== */ cy.location("pathname").should("eq", "/workflows"); @@ -62,7 +65,10 @@ describe("Auth", () => { cy.visit(pathProtected); cy.location("pathname").should("eq", pathLogin); - cy.location("search").should("eq", `?redirectUrl=${encodeURIComponent(pathProtected)}`); + cy.location("search").should( + "eq", + `?redirectUrl=${encodeURIComponent(pathProtected)}` + ); cy.get("#mat-input-0").type(email); cy.get("#mat-input-1").type(password); @@ -86,17 +92,24 @@ describe("Auth", () => { cy.authConnectAs(email, password); cy.visit(pathProtected); - cy.get("ng-component.ng-star-inserted > .flex-col > .flex-row > button").click(); + cy.get( + "ng-component.ng-star-inserted > .flex-col > .flex-row > button" + ).click(); cy.get(".mat-mdc-dialog-container #mat-input-0").type("newName"); cy.authDisconnect(); - cy.get(".mat-mdc-dialog-container form button[type=submit]").click(); + cy.get( + ".mat-mdc-dialog-container form button[type=submit]" + ).click(); // eslint-disable-next-line cypress/no-unnecessary-waiting -- Wait if there is a "redirection loop" cy.wait(125); cy.location("pathname").should("eq", pathLogin); - cy.location("search").should("eq", `?redirectUrl=${encodeURIComponent(pathProtected)}`); + cy.location("search").should( + "eq", + `?redirectUrl=${encodeURIComponent(pathProtected)}` + ); /* ==== Generated with Cypress Studio ==== */ cy.get(".mat-mdc-simple-snack-bar").should("be.visible"); diff --git a/apps/frontend-e2e/src/e2e/auth/profile.cy.ts b/apps/frontend-e2e/src/e2e/auth/profile.cy.ts index 0c1ff8e8..c58fc989 100644 --- a/apps/frontend-e2e/src/e2e/auth/profile.cy.ts +++ b/apps/frontend-e2e/src/e2e/auth/profile.cy.ts @@ -54,12 +54,13 @@ describe("Profile", () => { cy.get("#mat-input-1").type(toUpdate.firstname); cy.get("#mat-input-2").clear(); cy.get("#mat-input-2").type(`${toUpdate.lastname}{enter}`); - cy.get(".mat-mdc-dialog-actions > .mdc-button > .mdc-button__label").click(); + cy.get( + ".mat-mdc-dialog-actions > .mdc-button > .mdc-button__label" + ).click(); cy.get(".mat-toolbar .mat-mdc-button-touch-target").click(); - cy.get(".mat-mdc-menu-item > span.mat-mdc-menu-item-text:nth-child(1)").should( - "have.text", - `${toUpdate.firstname} ${toUpdate.lastname}` - ); + cy.get( + ".mat-mdc-menu-item > span.mat-mdc-menu-item-text:nth-child(1)" + ).should("have.text", `${toUpdate.firstname} ${toUpdate.lastname}`); /* ==== End Cypress Studio ==== */ }); }); diff --git a/apps/frontend-e2e/src/e2e/node/list.cy.ts b/apps/frontend-e2e/src/e2e/node/list.cy.ts index 173989e6..5d832c73 100644 --- a/apps/frontend-e2e/src/e2e/node/list.cy.ts +++ b/apps/frontend-e2e/src/e2e/node/list.cy.ts @@ -6,7 +6,9 @@ describe("Nodes list", () => { const dbHelper = DbE2eHelper.getHelper("base"); const db = dbHelper.db as typeof BASE_SEED; - const nodes = db.graph.nodes.filter(({ kind }) => kind.type === NodeKindType.TEMPLATE); + const nodes = db.graph.nodes.filter( + ({ kind }) => kind.type === NodeKindType.TEMPLATE + ); before(() => cy.dbRefresh("base")); @@ -22,11 +24,12 @@ describe("Nodes list", () => { const { _id } = nodes[1]; /* ==== Generated with Cypress Studio ==== */ - cy.get(".mdc-data-table__content > :nth-child(3) > .cdk-column-_id").should( - "have.text", - _id - ); - cy.get(".mdc-data-table__content > :nth-child(3) > .cdk-column-_id").click(); + cy.get( + ".mdc-data-table__content > :nth-child(3) > .cdk-column-_id" + ).should("have.text", _id); + cy.get( + ".mdc-data-table__content > :nth-child(3) > .cdk-column-_id" + ).click(); cy.get( ".mdc-data-table__content > :nth-child(4) .node-edit > .mat-mdc-button-touch-target" ).click(); @@ -44,26 +47,30 @@ describe("Nodes list", () => { const max = Math.max(...ids).toString(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - min - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "not.exist" - ); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", min); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("not.exist"); /* ==== End Cypress Studio ==== */ }); @@ -72,19 +79,24 @@ describe("Nodes list", () => { const max = Math.max(...ids).toString(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); // Makes wait for the query parameters FIXME: why is it needed here? cy.location("search").should("contains", `_id=asc`); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); // Makes wait for the query parameters FIXME: why is it needed here? cy.location("search").should("contains", `_id=desc`); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-name > ui-list-table-header > .align-i-center").click(); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-name > ui-list-table-header > .align-i-center" + ).click(); /* ==== End Cypress Studio ==== */ // Makes wait for the query parameters @@ -92,16 +104,15 @@ describe("Nodes list", () => { cy.reload(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); - cy.get(".cdk-column-name > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); + cy.get( + ".cdk-column-name > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); /* ==== End Cypress Studio ==== */ }); }); @@ -112,7 +123,9 @@ describe("Nodes list", () => { // Visit after dbRefresh to avoid a 500 error cy.visit("/nodes"); - cy.get("ng-component.ng-star-inserted > .flex-col > .flex-row > button").click(); + cy.get( + "ng-component.ng-star-inserted > .flex-col > .flex-row > button" + ).click(); }); it("should create a new node", () => { @@ -121,35 +134,40 @@ describe("Nodes list", () => { /* ==== Generated with Cypress Studio ==== */ cy.get(".mat-mdc-dialog-container #mat-input-0").type(newName); - cy.get(".mat-mdc-dialog-container .mat-mdc-select-placeholder").click(); + cy.get( + ".mat-mdc-dialog-container .mat-mdc-select-placeholder" + ).click(); cy.get(".mat-mdc-select-panel #mat-option-1").click(); - cy.get(".mat-mdc-dialog-container form button[type=submit]").click(); + cy.get( + ".mat-mdc-dialog-container form button[type=submit]" + ).click(); /* ==== End Cypress Studio ==== */ cy.location("pathname").should("eq", `/nodes/${newId}`); /* ==== Generated with Cypress Studio ==== */ cy.get('.mat-toolbar [routerlink="/nodes"]').click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); // Makes wait for the query parameters FIXME: why is it needed here? cy.location("search").should("contains", `_id=asc`); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); // Makes wait for the query parameters FIXME: why is it needed here? cy.location("search").should("contains", `_id=desc`); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - newId - ); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-name").should( - "have.text", - newName - ); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-behavior-type").should( - "have.text", - "function" - ); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", newId); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-name" + ).should("have.text", newName); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-behavior-type" + ).should("have.text", "function"); /* ==== End Cypress Studio ==== */ }); }); diff --git a/apps/frontend-e2e/src/e2e/workflows/list.cy.ts b/apps/frontend-e2e/src/e2e/workflows/list.cy.ts index 80fe4d50..6eb63418 100644 --- a/apps/frontend-e2e/src/e2e/workflows/list.cy.ts +++ b/apps/frontend-e2e/src/e2e/workflows/list.cy.ts @@ -22,26 +22,26 @@ describe("Workflows list", () => { const max = Math.max(...ids).toString(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - min - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", min); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); cy.get(".align-i-center > .ng-star-inserted > .mat-icon").click(); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); cy.get(".align-i-center > .ng-star-inserted").click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "not.exist" - ); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("not.exist"); /* ==== End Cypress Studio ==== */ }); @@ -51,15 +51,20 @@ describe("Workflows list", () => { const max = Math.max(...ids).toString(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center").click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center" + ).click(); // Makes wait for the query parameters cy.location("search").should("contains", `_id=desc`); - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-name > ui-list-table-header > .align-i-center").click(); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-name > ui-list-table-header > .align-i-center" + ).click(); /* ==== End Cypress Studio ==== */ // Makes wait for the query parameters @@ -67,16 +72,15 @@ describe("Workflows list", () => { cy.reload(); /* ==== Generated with Cypress Studio ==== */ - cy.get(".mdc-data-table__content > :nth-child(1) > .cdk-column-_id").should( - "have.text", - max - ); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); - cy.get(".cdk-column-name > ui-list-table-header > .align-i-center .mat-icon").should( - "be.visible" - ); + cy.get( + ".mdc-data-table__content > :nth-child(1) > .cdk-column-_id" + ).should("have.text", max); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); + cy.get( + ".cdk-column-name > ui-list-table-header > .align-i-center .mat-icon" + ).should("be.visible"); /* ==== End Cypress Studio ==== */ }); }); @@ -88,7 +92,10 @@ describe("Workflows list", () => { const { _id } = db.workflows[1]; /* ==== Generated with Cypress Studio ==== */ - cy.get(":nth-child(2) > .cdk-column-_id").should("have.text", _id.toString()); + cy.get(":nth-child(2) > .cdk-column-_id").should( + "have.text", + _id.toString() + ); cy.get(":nth-child(2) > .cdk-column-_id").click(); /* ==== End Cypress Studio ==== */ @@ -101,7 +108,9 @@ describe("Workflows list", () => { // Visit after dbRefresh to avoid a 500 error cy.visit("/workflows"); - cy.get("ng-component.ng-star-inserted > .flex-col > .flex-row > button").click(); + cy.get( + "ng-component.ng-star-inserted > .flex-col > .flex-row > button" + ).click(); }); it("should create a new workflow", () => { @@ -112,28 +121,38 @@ describe("Workflows list", () => { /* ==== Generated with Cypress Studio ==== */ cy.get(".mat-mdc-dialog-container #mat-input-0").type(newName); - cy.get(".mat-mdc-dialog-container form button[type=submit]").click(); + cy.get( + ".mat-mdc-dialog-container form button[type=submit]" + ).click(); /* ==== End Cypress Studio ==== */ cy.location("pathname").should("eq", `/workflows/${newId}`); /* ==== Generated with Cypress Studio ==== */ cy.get('.mat-toolbar [routerlink="/workflows"]').click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get(".cdk-column-_id > ui-list-table-header > .align-i-center > span").click(); - cy.get('[ng-reflect-router-link="/workflows/4"] > .cdk-column-name').should( - "have.text", - newName - ); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + ".cdk-column-_id > ui-list-table-header > .align-i-center > span" + ).click(); + cy.get( + '[ng-reflect-router-link="/workflows/4"] > .cdk-column-name' + ).should("have.text", newName); /* ==== End Cypress Studio ==== */ }); it("should indicate that a name is already taken", () => { const [workflow] = db.workflows; - cy.get(".mat-mdc-dialog-container #mat-input-0").type(workflow.name); + cy.get(".mat-mdc-dialog-container #mat-input-0").type( + workflow.name + ); /* ==== Generated with Cypress Studio ==== */ - cy.get(".mat-mdc-dialog-container .gap-1 > .mat-warn").should("contain.text", "close"); + cy.get(".mat-mdc-dialog-container .gap-1 > .mat-warn").should( + "contain.text", + "close" + ); /* ==== End Cypress Studio ==== */ }); }); diff --git a/apps/frontend-e2e/src/e2e/workflows/view.cy.ts b/apps/frontend-e2e/src/e2e/workflows/view.cy.ts index ffa2fe46..8833dc05 100644 --- a/apps/frontend-e2e/src/e2e/workflows/view.cy.ts +++ b/apps/frontend-e2e/src/e2e/workflows/view.cy.ts @@ -25,10 +25,9 @@ describe("Workflow view", () => { const text = " 123"; /* ==== Generated with Cypress Studio ==== */ - cy.get("mat-tab-header #mat-tab-label-0-0 > .mdc-tab__content > span span").should( - "contain", - workflowActionable.name - ); + cy.get( + "mat-tab-header #mat-tab-label-0-0 > .mdc-tab__content > span span" + ).should("contain", workflowActionable.name); // Update cy.get("app-workflow-update-card #mat-input-0").type(text); @@ -61,10 +60,9 @@ describe("Workflow view", () => { cy.wait("@getWorkflow"); /* ==== Generated with Cypress Studio ==== */ - cy.get("mat-tab-header #mat-tab-label-0-0 > .mdc-tab__content > span span").should( - "contain", - "An error occurred" - ); + cy.get( + "mat-tab-header #mat-tab-label-0-0 > .mdc-tab__content > span span" + ).should("contain", "An error occurred"); cy.get("ui-http-error-card mat-card-subtitle > span").should( "have.text", "The resource was not found." diff --git a/apps/frontend-e2e/src/support/commands.ts b/apps/frontend-e2e/src/support/commands.ts index 7f2417d6..d8f474bd 100644 --- a/apps/frontend-e2e/src/support/commands.ts +++ b/apps/frontend-e2e/src/support/commands.ts @@ -11,24 +11,35 @@ import { DbE2eHelper } from "~/app/backend/e2e/db-e2e/db-e2e.helper"; import { DbTestSample } from "~/app/backend/test/db-test"; import { AuthLoginDto } from "~/lib/common/app/auth/dtos"; -import { AUTH_ENDPOINT_PREFIX, AuthEndpoints } from "~/lib/common/app/auth/endpoints"; +import { + AUTH_ENDPOINT_PREFIX, + AuthEndpoints +} from "~/lib/common/app/auth/endpoints"; // TODO: a way to get this from configuration? const e2eApi = "http://127.0.0.1:32300/e2e/api"; const authConnectAs = (email: string, password: string) => - cy.request("post", `${e2eApi}${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.LOGIN}`, { - cookie: true, - email, - password - } satisfies AuthLoginDto); + cy.request( + "post", + `${e2eApi}${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.LOGIN}`, + { + cookie: true, + email, + password + } satisfies AuthLoginDto + ); const authDisconnect = () => cy - .request("post", `${e2eApi}${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.LOGOUT}`) + .request( + "post", + `${e2eApi}${AUTH_ENDPOINT_PREFIX}/${AuthEndpoints.LOGOUT}` + ) .then(() => cy.clearCookies()); -const dbRefresh = (sample: DbTestSample) => cy.wrap(DbE2eHelper.getHelper(sample).refresh()); +const dbRefresh = (sample: DbTestSample) => + cy.wrap(DbE2eHelper.getHelper(sample).refresh()); declare global { // eslint-disable-next-line @typescript-eslint/no-namespace -- same as above diff --git a/apps/frontend-e2e/tsconfig.json b/apps/frontend-e2e/tsconfig.json index 15f465af..7e1a9b32 100644 --- a/apps/frontend-e2e/tsconfig.json +++ b/apps/frontend-e2e/tsconfig.json @@ -5,8 +5,13 @@ "outDir": "../../dist/out-tsc", "sourceMap": false, "target": "ES6", - "types": ["cypress", "node", "@4tw/cypress-drag-drop", "@testing-library/cypress"] + "types": [ + "@4tw/cypress-drag-drop", + "@testing-library/cypress", + "cypress", + "node" + ] }, "extends": "../../tsconfig.e2e.json", - "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] + "include": ["cypress.config.ts", "src/**/*.js", "src/**/*.ts"] } diff --git a/apps/frontend/.storybook/preview-head.html b/apps/frontend/.storybook/preview-head.html index 7a7132e4..57d4a531 100644 --- a/apps/frontend/.storybook/preview-head.html +++ b/apps/frontend/.storybook/preview-head.html @@ -4,5 +4,8 @@ href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet" /> - + diff --git a/apps/frontend/.storybook/tsconfig.json b/apps/frontend/.storybook/tsconfig.json index bc4e4b8c..083252f0 100644 --- a/apps/frontend/.storybook/tsconfig.json +++ b/apps/frontend/.storybook/tsconfig.json @@ -6,12 +6,12 @@ "exclude": ["../**/*.spec.ts", "../src/dev/**/*"], "extends": "../tsconfig.json", "include": [ - "../src/**/*.stories.ts", + "*.js", + "*.ts", + "../../../libs/ng/src/**/*.stories.ts", + "../src/**/*.component.ts", "../src/**/*.stories.js", "../src/**/*.stories.mdx", - "../src/**/*.component.ts", - "*.ts", - "*.js", - "../../../libs/ng/src/**/*.stories.ts" + "../src/**/*.stories.ts" ] } diff --git a/apps/frontend/project.json b/apps/frontend/project.json index 236a5401..2ffb88e5 100644 --- a/apps/frontend/project.json +++ b/apps/frontend/project.json @@ -50,7 +50,10 @@ "executor": "@angular-devkit/build-angular:browser", "options": { "allowedCommonJsDependencies": ["validator"], - "assets": ["apps/frontend/src/favicon.ico", "apps/frontend/src/assets"], + "assets": [ + "apps/frontend/src/favicon.ico", + "apps/frontend/src/assets" + ], "index": "apps/frontend/src/index.html", "inlineStyleLanguage": "scss", "main": "apps/frontend/src/main.ts", @@ -81,7 +84,9 @@ "lint": { "executor": "@nx/linter:eslint", "options": { - "lintFilePatterns": ["apps/frontend/**/*.{ts,js,html,md,json,yml}"] + "lintFilePatterns": [ + "apps/frontend/**/*.{ts,js,html,md,json,yml}" + ] }, "outputs": ["{options.outputFile}"] }, diff --git a/apps/frontend/src/app/_layout/header/header.component.ts b/apps/frontend/src/app/_layout/header/header.component.ts index 5c41baaf..392ffb45 100644 --- a/apps/frontend/src/app/_layout/header/header.component.ts +++ b/apps/frontend/src/app/_layout/header/header.component.ts @@ -59,7 +59,9 @@ export class HeaderComponent { * @returns Promise */ protected handleLogout() { - return this.authService.logout().then(() => this.router.navigateByUrl("/auth/login")); + return this.authService + .logout() + .then(() => this.router.navigateByUrl("/auth/login")); } /** @@ -68,7 +70,9 @@ export class HeaderComponent { * @param user the connected user */ protected async openProfile(user: UserDto) { - const { ProfileDialog } = await import("../../auth/dialogs/profile/profile.dialog"); + const { ProfileDialog } = await import( + "../../auth/dialogs/profile/profile.dialog" + ); ProfileDialog.open(this.matDialog, { user }); } diff --git a/apps/frontend/src/app/app.module.ts b/apps/frontend/src/app/app.module.ts index def324f2..4d51d409 100644 --- a/apps/frontend/src/app/app.module.ts +++ b/apps/frontend/src/app/app.module.ts @@ -1,4 +1,9 @@ -import { APP_INITIALIZER, ApplicationRef, DoBootstrap, NgModule } from "@angular/core"; +import { + APP_INITIALIZER, + ApplicationRef, + DoBootstrap, + NgModule +} from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ApiModule } from "~/lib/ng/lib/api"; @@ -28,14 +33,15 @@ import { AppTranslationModule } from "../lib/translation"; deps: [AuthInterceptor, AuthService], multi: true, provide: APP_INITIALIZER, - useFactory: (interceptor: AuthInterceptor, service: AuthService) => () => - interceptor.runUnprotected(() => - service.refresh().catch((error: unknown) => { - if (!AuthService.isAnUnauthorizedError(error)) { - throw error; - } - }) - ) + useFactory: + (interceptor: AuthInterceptor, service: AuthService) => () => + interceptor.runUnprotected(() => + service.refresh().catch((error: unknown) => { + if (!AuthService.isAnUnauthorizedError(error)) { + throw error; + } + }) + ) } ] }) diff --git a/apps/frontend/src/app/app.routes.ts b/apps/frontend/src/app/app.routes.ts index 91a71ffa..62752ce4 100644 --- a/apps/frontend/src/app/app.routes.ts +++ b/apps/frontend/src/app/app.routes.ts @@ -5,19 +5,25 @@ import { authGuard } from "./auth/auth.guard"; export const APP_ROUTES: Routes = [ { loadChildren: () => - import("./auth/views/auth-routing.module").then(m => m.AuthRoutingModule), + import("./auth/views/auth-routing.module").then( + m => m.AuthRoutingModule + ), path: "auth" }, { canActivate: [authGuard], loadChildren: () => - import("./node/views/node-routing.module").then(m => m.NodeRoutingModule), + import("./node/views/node-routing.module").then( + m => m.NodeRoutingModule + ), path: "nodes" }, { canActivate: [authGuard], loadChildren: () => - import("./workflow/views/workflow-routing.module").then(m => m.WorkflowRoutingModule), + import("./workflow/views/workflow-routing.module").then( + m => m.WorkflowRoutingModule + ), path: "workflows" }, { @@ -29,7 +35,10 @@ export const APP_ROUTES: Routes = [ redirectTo: "workflows" }, { - loadComponent: () => import("./_views/not-found/not-found.view").then(m => m.NotFoundView), + loadComponent: () => + import("./_views/not-found/not-found.view").then( + m => m.NotFoundView + ), path: "**" } ]; diff --git a/apps/frontend/src/app/auth/auth.guard.ts b/apps/frontend/src/app/auth/auth.guard.ts index 3775214a..a8e678a3 100644 --- a/apps/frontend/src/app/auth/auth.guard.ts +++ b/apps/frontend/src/app/auth/auth.guard.ts @@ -20,7 +20,9 @@ export const authGuard: CanActivateChildFn & CanActivateFn = (route, state) => { return service.userState$.pipe( map(({ type }) => - type === "connected" ? true : AuthService.createLoginUrlTree(router, state.url) + type === "connected" + ? true + : AuthService.createLoginUrlTree(router, state.url) ) ); }; diff --git a/apps/frontend/src/app/auth/auth.interceptor.ts b/apps/frontend/src/app/auth/auth.interceptor.ts index 4824de8b..028dcbe0 100644 --- a/apps/frontend/src/app/auth/auth.interceptor.ts +++ b/apps/frontend/src/app/auth/auth.interceptor.ts @@ -1,4 +1,9 @@ -import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from "@angular/common/http"; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor +} from "@angular/common/http"; import { Injectable } from "@angular/core"; import { MatSnackBar } from "@angular/material/snack-bar"; import { Router } from "@angular/router"; @@ -17,7 +22,9 @@ export class AuthInterceptor implements HttpInterceptor { /** * The basic path to the login component */ - private readonly pathLogin = AuthService.createLoginUrlTree(this.router).toString(); + private readonly pathLogin = AuthService.createLoginUrlTree( + this.router + ).toString(); /** * Constructor with "dependency injection" @@ -62,7 +69,10 @@ export class AuthInterceptor implements HttpInterceptor { this.service.disconnectUser(); } - if (this.protected && AuthService.isAnUnauthorizedError(error)) { + if ( + this.protected && + AuthService.isAnUnauthorizedError(error) + ) { const { url } = this.router.routerState.snapshot; // TODO: better? if (!url.startsWith(this.pathLogin)) { @@ -86,13 +96,21 @@ export class AuthInterceptor implements HttpInterceptor { this.snackBar.open( (await lastValueFrom( type === "connected" - ? this.translateService.get("interceptors.auth.redirect.connected") - : this.translateService.get("interceptors.auth.redirect.unconnected") + ? this.translateService.get( + "interceptors.auth.redirect.connected" + ) + : this.translateService.get( + "interceptors.auth.redirect.unconnected" + ) + )) as string, + (await lastValueFrom( + this.translateService.get("actions.ok") )) as string, - (await lastValueFrom(this.translateService.get("actions.ok"))) as string, { duration: 5000, horizontalPosition: "end" } ); - await this.router.navigateByUrl(AuthService.createLoginUrlTree(this.router, redirect)); + await this.router.navigateByUrl( + AuthService.createLoginUrlTree(this.router, redirect) + ); } } diff --git a/apps/frontend/src/app/auth/auth.module.ts b/apps/frontend/src/app/auth/auth.module.ts index 0a4d7231..4187b2f9 100644 --- a/apps/frontend/src/app/auth/auth.module.ts +++ b/apps/frontend/src/app/auth/auth.module.ts @@ -12,7 +12,11 @@ import { AuthService } from "./auth.service"; providers: [ AuthInterceptor, AuthService, - { multi: true, provide: HTTP_INTERCEPTORS, useExisting: AuthInterceptor } + { + multi: true, + provide: HTTP_INTERCEPTORS, + useExisting: AuthInterceptor + } ] }) export class AuthModule {} diff --git a/apps/frontend/src/app/auth/auth.service.ts b/apps/frontend/src/app/auth/auth.service.ts index 55436590..8b767f39 100644 --- a/apps/frontend/src/app/auth/auth.service.ts +++ b/apps/frontend/src/app/auth/auth.service.ts @@ -14,7 +14,8 @@ export type AuthLogin = Omit; /** * When the user of the {@link AuthService} is connected */ -export interface AuthUserStateConnected extends Pick { +export interface AuthUserStateConnected + extends Pick { /** * The connected user */ @@ -46,7 +47,9 @@ export class AuthService { * * // FIXME */ - private static readonly userState = new BehaviorSubject({ type: "unconnected" }); + private static readonly userState = new BehaviorSubject({ + type: "unconnected" + }); /** * Creates a URLTree for the LoginView @@ -67,8 +70,13 @@ export class AuthService { * @param error the error to test * @returns if the error is an HTTP Unauthorized */ - public static isAnUnauthorizedError(error: unknown): error is HttpErrorResponse { - return error instanceof HttpErrorResponse && error.status === HttpStatusCode.Unauthorized; + public static isAnUnauthorizedError( + error: unknown + ): error is HttpErrorResponse { + return ( + error instanceof HttpErrorResponse && + error.status === HttpStatusCode.Unauthorized + ); } /** @@ -146,7 +154,10 @@ export class AuthService { */ private onAuthSuccess(success: AuthSuccessDto) { return this.apiService.getProfile().then(user => { - const state: AuthUserStateConnected = { expires_at: success.expires_at, user }; + const state: AuthUserStateConnected = { + expires_at: success.expires_at, + user + }; this.userState.next({ ...state, type: "connected" }); return state; }); diff --git a/apps/frontend/src/app/auth/components/login-card/login-card.component.html b/apps/frontend/src/app/auth/components/login-card/login-card.component.html index eeb6c8d8..0624082a 100644 --- a/apps/frontend/src/app/auth/components/login-card/login-card.component.html +++ b/apps/frontend/src/app/auth/components/login-card/login-card.component.html @@ -1,14 +1,26 @@ - components.login.title + components.login.title -
+ entities.user.email - - {{ form.controls.email.errors | translateControlError }} + + {{ + form.controls.email.errors | translateControlError + }} @@ -31,15 +43,25 @@ visibility - {{ form.controls.password.errors | translateControlError }} + {{ + form.controls.password.errors | translateControlError + }} - - + error_outline {{ errorMessage(error) | async }} diff --git a/apps/frontend/src/app/auth/components/login-card/login-card.component.stories.ts b/apps/frontend/src/app/auth/components/login-card/login-card.component.stories.ts index ed7006e1..97819956 100644 --- a/apps/frontend/src/app/auth/components/login-card/login-card.component.stories.ts +++ b/apps/frontend/src/app/auth/components/login-card/login-card.component.stories.ts @@ -20,7 +20,9 @@ export const TestButtonDisabled: Story = { play: async ({ canvasElement }) => { const txtEmail = canvasElement.querySelector("[name=email]")!; const txtPassword = canvasElement.querySelector("[name=password]")!; - const btnLogin = canvasElement.querySelector("button[type=submit]")!; + const btnLogin = canvasElement.querySelector( + "button[type=submit]" + )!; await expect(btnLogin.disabled).toBe(true); diff --git a/apps/frontend/src/app/auth/components/login-card/login-card.component.ts b/apps/frontend/src/app/auth/components/login-card/login-card.component.ts index 5421dd1c..871d0a89 100644 --- a/apps/frontend/src/app/auth/components/login-card/login-card.component.ts +++ b/apps/frontend/src/app/auth/components/login-card/login-card.component.ts @@ -1,6 +1,12 @@ import { CommonModule } from "@angular/common"; import { HttpErrorResponse } from "@angular/common/http"; -import { Component, EventEmitter, HostListener, Input, Output } from "@angular/core"; +import { + Component, + EventEmitter, + HostListener, + Input, + Output +} from "@angular/core"; import { FormControl, FormGroup, diff --git a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.html b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.html index 67b91df4..9ecbd0f7 100644 --- a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.html +++ b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.html @@ -3,7 +3,10 @@

dialogs.profile.title

{{ user | userDisplayName }}

- + entities.user.email @@ -22,7 +25,12 @@

{{ user | userDisplayName }}

-
@@ -38,5 +46,7 @@

{{ user | userDisplayName }}

- +
diff --git a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.spec.ts b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.spec.ts index d7aa681a..94f71c32 100644 --- a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.spec.ts +++ b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.spec.ts @@ -13,7 +13,9 @@ describe("ProfileDialog", () => { imports: [ProfileDialog] }) .overrideProvider(MAT_DIALOG_DATA, { - useValue: { user: BASE_SEED.users[0] } satisfies ProfileDialogData + useValue: { + user: BASE_SEED.users[0] + } satisfies ProfileDialogData }) .compileComponents(); diff --git a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.stories.ts b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.stories.ts index 62c9b6d8..499351bf 100644 --- a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.stories.ts +++ b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.stories.ts @@ -19,7 +19,9 @@ export const Primary: Story = { providers: [ { provide: MAT_DIALOG_DATA, - useValue: { user: BASE_SEED.users[0] } satisfies ProfileDialogData + useValue: { + user: BASE_SEED.users[0] + } satisfies ProfileDialogData } ] }) diff --git a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.ts b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.ts index 75879ef6..885eeefd 100644 --- a/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.ts +++ b/apps/frontend/src/app/auth/dialogs/profile/profile.dialog.ts @@ -1,6 +1,11 @@ import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; -import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule +} from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; import { MAT_DIALOG_DATA, @@ -56,10 +61,13 @@ export class ProfileDialog { data: ProfileDialogData, config?: Omit ) { - return matDialog.open(ProfileDialog, { - ...config, - data - }); + return matDialog.open( + ProfileDialog, + { + ...config, + data + } + ); } protected readonly form: FormGroup< @@ -69,10 +77,15 @@ export class ProfileDialog { /** * Handles the update of the form */ - protected readonly requestStateUpdate$ = new RequestStateSubject((dto: UserUpdateDto) => - this.userApi - .update(this.dialogData.user._id, dto) - .then(() => this.authService.refresh().then(({ user }) => (this.user = user))) + protected readonly requestStateUpdate$ = new RequestStateSubject( + (dto: UserUpdateDto) => + this.userApi + .update(this.dialogData.user._id, dto) + .then(() => + this.authService + .refresh() + .then(({ user }) => (this.user = user)) + ) ); /** diff --git a/apps/frontend/src/app/auth/views/login/login.view.html b/apps/frontend/src/app/auth/views/login/login.view.html index df135d38..23cfe8d2 100644 --- a/apps/frontend/src/app/auth/views/login/login.view.html +++ b/apps/frontend/src/app/auth/views/login/login.view.html @@ -20,6 +20,8 @@ - views.login.being-redirected + views.login.being-redirected diff --git a/apps/frontend/src/app/auth/views/login/login.view.spec.ts b/apps/frontend/src/app/auth/views/login/login.view.spec.ts index 28b1f267..c656d070 100644 --- a/apps/frontend/src/app/auth/views/login/login.view.spec.ts +++ b/apps/frontend/src/app/auth/views/login/login.view.spec.ts @@ -10,7 +10,11 @@ describe("LoginView", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [LoginView, NoopAnimationsModule, TranslateTestingModule.withTranslations({})] + imports: [ + LoginView, + NoopAnimationsModule, + TranslateTestingModule.withTranslations({}) + ] }).compileComponents(); fixture = TestBed.createComponent(LoginView); diff --git a/apps/frontend/src/app/auth/views/login/login.view.stories.ts b/apps/frontend/src/app/auth/views/login/login.view.stories.ts index 1ef9442e..787ccf71 100644 --- a/apps/frontend/src/app/auth/views/login/login.view.stories.ts +++ b/apps/frontend/src/app/auth/views/login/login.view.stories.ts @@ -7,7 +7,8 @@ const meta: Meta = { component: LoginView, decorators: [ componentWrapperDecorator( - story => `
${story}
` + story => + `
${story}
` ) ], title: "Auth/views/Login" diff --git a/apps/frontend/src/app/auth/views/login/login.view.ts b/apps/frontend/src/app/auth/views/login/login.view.ts index 08005f35..c78d4de5 100644 --- a/apps/frontend/src/app/auth/views/login/login.view.ts +++ b/apps/frontend/src/app/auth/views/login/login.view.ts @@ -11,7 +11,10 @@ import { TranslationModule } from "~/lib/ng/lib/translation"; import { AuthInterceptor } from "../../auth.interceptor"; import { AuthModule } from "../../auth.module"; import { AuthService } from "../../auth.service"; -import { AuthLogin, LoginCardComponent } from "../../components/login-card/login-card.component"; +import { + AuthLogin, + LoginCardComponent +} from "../../components/login-card/login-card.component"; @Component({ standalone: true, @@ -34,8 +37,9 @@ export class LoginView implements OnInit, OnDestroy { @Input() public redirectUrl?: string; - protected readonly loginState$ = new RequestStateSubject((login: AuthLogin) => - this.interceptor.runUnprotected(() => this.service.login(login)) + protected readonly loginState$ = new RequestStateSubject( + (login: AuthLogin) => + this.interceptor.runUnprotected(() => this.service.login(login)) ); protected loginState = this.loginState$.getValue(); @@ -69,7 +73,9 @@ export class LoginView implements OnInit, OnDestroy { return; } - this.subscription.add(this.loginState$.subscribe(state => (this.loginState = state))); + this.subscription.add( + this.loginState$.subscribe(state => (this.loginState = state)) + ); } /** @inheritDoc */ public ngOnDestroy() { @@ -92,6 +98,12 @@ export class LoginView implements OnInit, OnDestroy { private redirect(user: UserDto, redirectUrl = "/") { this.redirecting = user; // Some time to let the animation - setTimeout(() => void this.router.navigateByUrl(redirectUrl, { replaceUrl: true }), 1250); + setTimeout( + () => + void this.router.navigateByUrl(redirectUrl, { + replaceUrl: true + }), + 1250 + ); } } diff --git a/apps/frontend/src/app/graph/components/editor/graph-editor.component.spec.ts b/apps/frontend/src/app/graph/components/editor/graph-editor.component.spec.ts index ae380ddd..2169b273 100644 --- a/apps/frontend/src/app/graph/components/editor/graph-editor.component.spec.ts +++ b/apps/frontend/src/app/graph/components/editor/graph-editor.component.spec.ts @@ -14,7 +14,10 @@ describe("GraphEditorComponent", () => { fixture = TestBed.createComponent(GraphEditorComponent); component = fixture.componentInstance; - component.nodes$ = of({ snapshot: { isLoading: false }, state: "init" }); + component.nodes$ = of({ + snapshot: { isLoading: false }, + state: "init" + }); fixture.detectChanges(); }); diff --git a/apps/frontend/src/app/graph/components/editor/graph-editor.component.stories.ts b/apps/frontend/src/app/graph/components/editor/graph-editor.component.stories.ts index 7b74b3bc..a304d225 100644 --- a/apps/frontend/src/app/graph/components/editor/graph-editor.component.stories.ts +++ b/apps/frontend/src/app/graph/components/editor/graph-editor.component.stories.ts @@ -20,17 +20,21 @@ import { GraphComponent } from "../graph/graph.component"; const db = jsonify(BASE_SEED); const { nodes } = db.graph; -const getGraphContent = (graph: GraphJSON): Pick => { +const getGraphContent = ( + graph: GraphJSON +): Pick => { const { arcs: gArcs, nodes: gNodes } = db.graph; const nodes = gNodes.filter( - ({ kind }) => kind.type === NodeKindType.VERTEX && kind.__graph === graph._id + ({ kind }) => + kind.type === NodeKindType.VERTEX && kind.__graph === graph._id ); const arcs = gArcs.filter(({ __from, __to }) => nodes.some( ({ inputs, outputs }) => - inputs.some(({ _id }) => _id === __to) || outputs.some(({ _id }) => _id === __from) + inputs.some(({ _id }) => _id === __to) || + outputs.some(({ _id }) => _id === __from) ) ); @@ -41,7 +45,11 @@ const getGraphContent = (graph: GraphJSON): Pick Promise.resolve().then(() => { diff --git a/apps/frontend/src/app/graph/components/editor/graph-editor.component.ts b/apps/frontend/src/app/graph/components/editor/graph-editor.component.ts index bb492bc0..ab508091 100644 --- a/apps/frontend/src/app/graph/components/editor/graph-editor.component.ts +++ b/apps/frontend/src/app/graph/components/editor/graph-editor.component.ts @@ -1,11 +1,25 @@ import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + Output, + ViewChild +} from "@angular/core"; import { Observable } from "rxjs"; import { PositionDto } from "~/lib/common/app/node/dtos/position.dto"; import { NodeJSON } from "~/lib/common/app/node/endpoints"; -import { NodeSelectorComponent, NodeSelectorNodes } from "./node-selector/node-selector.component"; -import { GraphActions, GraphComponent, GraphData, NodeMoved } from "../graph/graph.component"; +import { + NodeSelectorComponent, + NodeSelectorNodes +} from "./node-selector/node-selector.component"; +import { + GraphActions, + GraphComponent, + GraphData, + NodeMoved +} from "../graph/graph.component"; export interface GraphEditorNodeToAdd { /** Node-template to add */ @@ -56,6 +70,9 @@ export class GraphEditorComponent { * @param node that was selected */ protected handleNodeSelected(node: NodeJSON) { - this.nodeToAdd.emit({ node, position: this.graphComponent.getCurrentViewPort().middle }); + this.nodeToAdd.emit({ + node, + position: this.graphComponent.getCurrentViewPort().middle + }); } } diff --git a/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.spec.ts b/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.spec.ts index 3a03b210..bc1b4e30 100644 --- a/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.spec.ts +++ b/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.spec.ts @@ -14,7 +14,10 @@ describe("NodeSelectorComponent", () => { fixture = TestBed.createComponent(NodeSelectorComponent); component = fixture.componentInstance; - component.nodes$ = of({ snapshot: { isLoading: false }, state: "init" }); + component.nodes$ = of({ + snapshot: { isLoading: false }, + state: "init" + }); fixture.detectChanges(); }); diff --git a/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.ts b/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.ts index 12a6e08f..6c3dbad9 100644 --- a/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.ts +++ b/apps/frontend/src/app/graph/components/editor/node-selector/node-selector.component.ts @@ -36,8 +36,12 @@ export class NodeSelectorComponent { public readonly nodeSelected = new EventEmitter(); /** Columns to show in the selector */ - protected readonly NODE_COLUMNS: readonly NodeListColumn[] = ["name", "actions.expansion"]; + protected readonly NODE_COLUMNS: readonly NodeListColumn[] = [ + "name", + "actions.expansion" + ]; /** @internal */ - protected handleNodeRowClick = (node: NodeJSON) => this.nodeSelected.emit(node); + protected handleNodeRowClick = (node: NodeJSON) => + this.nodeSelected.emit(node); } diff --git a/apps/frontend/src/app/graph/components/graph/graph.component.scss b/apps/frontend/src/app/graph/components/graph/graph.component.scss index 2f8e5f30..f65f493c 100644 --- a/apps/frontend/src/app/graph/components/graph/graph.component.scss +++ b/apps/frontend/src/app/graph/components/graph/graph.component.scss @@ -34,10 +34,21 @@ inset: (-$size-h * 50) (-$size-w * 50); - background-image: linear-gradient($color-major $line-width, transparent $line-width), - linear-gradient(90deg, $color-major $line-width, transparent $line-width), + background-image: linear-gradient( + $color-major $line-width, + transparent $line-width + ), + linear-gradient( + 90deg, + $color-major $line-width, + transparent $line-width + ), linear-gradient($color-minor $line-width, transparent $line-width), - linear-gradient(90deg, $color-minor $line-width, transparent $line-width); + linear-gradient( + 90deg, + $color-minor $line-width, + transparent $line-width + ); background-position: 0 0, $line-offset $line-offset; diff --git a/apps/frontend/src/app/graph/components/graph/graph.component.stories.ts b/apps/frontend/src/app/graph/components/graph/graph.component.stories.ts index 0fbffa87..38795af2 100644 --- a/apps/frontend/src/app/graph/components/graph/graph.component.stories.ts +++ b/apps/frontend/src/app/graph/components/graph/graph.component.stories.ts @@ -7,17 +7,21 @@ import { jsonify } from "~/lib/common/utils/jsonify"; import { GraphComponent } from "./graph.component"; -const getGraphContent = (graph: GraphDto): Pick => { +const getGraphContent = ( + graph: GraphDto +): Pick => { const { arcs: gArcs, nodes: gNodes } = jsonify(BASE_SEED.graph); const nodes = gNodes.filter( - ({ kind }) => kind.type === NodeKindType.VERTEX && kind.__graph === graph._id + ({ kind }) => + kind.type === NodeKindType.VERTEX && kind.__graph === graph._id ); const arcs = gArcs.filter(({ __from, __to }) => nodes.some( ({ inputs, outputs }) => - inputs.some(({ _id }) => _id === __to) || outputs.some(({ _id }) => _id === __from) + inputs.some(({ _id }) => _id === __to) || + outputs.some(({ _id }) => _id === __from) ) ); @@ -28,7 +32,11 @@ const getGraphContent = (graph: GraphDto): Pick Promise.resolve().then(() => { diff --git a/apps/frontend/src/app/graph/components/graph/graph.component.ts b/apps/frontend/src/app/graph/components/graph/graph.component.ts index aaedde2e..3faa471e 100644 --- a/apps/frontend/src/app/graph/components/graph/graph.component.ts +++ b/apps/frontend/src/app/graph/components/graph/graph.component.ts @@ -11,9 +11,16 @@ import { ViewChild } from "@angular/core"; import { GetSchemes, NodeEditor, Root } from "rete"; -import { AngularArea2D, AngularPlugin, Presets as AngularPresets } from "rete-angular-plugin/16"; +import { + AngularArea2D, + AngularPlugin, + Presets as AngularPresets +} from "rete-angular-plugin/16"; import { Area2D, AreaExtensions, AreaPlugin } from "rete-area-plugin"; -import { ConnectionPlugin, Presets as ConnectionPresets } from "rete-connection-plugin"; +import { + ConnectionPlugin, + Presets as ConnectionPresets +} from "rete-connection-plugin"; import { ReadonlyPlugin } from "rete-readonly-plugin"; import { bufferToggle, filter, map, Observable, Subject } from "rxjs"; import { GraphArcCreateDto } from "~/lib/common/app/graph/dtos/arc"; @@ -21,7 +28,12 @@ import { GraphArcJSON, GraphNodeJSON } from "~/lib/common/app/graph/endpoints"; import { NodeKindType } from "~/lib/common/app/node/dtos/kind/node-kind.type"; import { PositionDto } from "~/lib/common/app/node/dtos/position.dto"; import { NodeJSON } from "~/lib/common/app/node/endpoints"; -import { ReteConnection, ReteInput, ReteNode, ReteOutput } from "~/lib/ng/lib/rete"; +import { + ReteConnection, + ReteInput, + ReteNode, + ReteOutput +} from "~/lib/ng/lib/rete"; import { ReteConnectionComponent } from "../../rete/connection/rete.connection.component"; import { ReteNodeComponent } from "../../rete/node/rete.node.component"; @@ -152,22 +164,30 @@ export class GraphComponent implements OnDestroy, OnChanges { * Observable on the area events. * To pipe and use only what is needed */ - private readonly area$ = new Subject | AreaExtra | Root>(); + private readonly area$ = new Subject< + Area2D | AreaExtra | Root + >(); private destroyed = false; public constructor(private readonly injector: Injector) { this.nodeMoved = this.area$.pipe( // Only want the translation and when the node is dragged - filter(({ type }) => type === "nodetranslated" || type === "nodedragged"), + filter( + ({ type }) => + type === "nodetranslated" || type === "nodedragged" + ), bufferToggle( // Start buffering when the node is picked this.area$.pipe(filter(({ type }) => type === "nodepicked")), // And stop when it is dragged - () => this.area$.pipe(filter(({ type }) => type === "nodedragged")) + () => + this.area$.pipe( + filter(({ type }) => type === "nodedragged") + ) ), // Ignore when there is not enough emits - filter(buffer => buffer.length >= 3), + filter(buffer => 3 <= buffer.length), map(([previous, ...rest]) => { const [node, current] = rest.slice().reverse(); @@ -187,7 +207,10 @@ export class GraphComponent implements OnDestroy, OnChanges { throw new Error("Should not happen"); }), // Ignore if the node is just clicked or been moved to the same place - filter(({ current, previous }) => previous.x !== current.x || previous.y !== current.y) + filter( + ({ current, previous }) => + previous.x !== current.x || previous.y !== current.y + ) ); } @@ -238,8 +261,13 @@ export class GraphComponent implements OnDestroy, OnChanges { } = area; const [posX, posY] = [x, y].map(v => Math.round(-v)); - const [height, width] = [offsetHeight, offsetWidth].map(v => Math.round(v / zoom)); - const [midX, midY] = [Math.round(posX + width / 2), Math.round(posY + height / 2)]; + const [height, width] = [offsetHeight, offsetWidth].map(v => + Math.round(v / zoom) + ); + const [midX, midY] = [ + Math.round(posX + width / 2), + Math.round(posY + height / 2) + ]; return { height, middle: { x: midX, y: midY }, @@ -253,7 +281,9 @@ export class GraphComponent implements OnDestroy, OnChanges { private async initRete() { this.destroyed = false; - const area = new AreaPlugin(this.container.nativeElement); + const area = new AreaPlugin( + this.container.nativeElement + ); const connection = new ConnectionPlugin(); const editor = new NodeEditor(); const readonlyPlugin = new ReadonlyPlugin(); @@ -261,7 +291,9 @@ export class GraphComponent implements OnDestroy, OnChanges { this.rete = { area, editor, readonly: readonlyPlugin }; // Set editor - const render = new AngularPlugin({ injector: this.injector }); + const render = new AngularPlugin({ + injector: this.injector + }); render.addPreset( AngularPresets.classic.setup({ @@ -305,7 +337,9 @@ export class GraphComponent implements OnDestroy, OnChanges { inputsMap.set(input.input._id, input); } - for (const output of Object.values(reteNode.outputs) as ReteOutput[]) { + for (const output of Object.values( + reteNode.outputs + ) as ReteOutput[]) { outputsMap.set(output.output._id, output); } @@ -320,7 +354,9 @@ export class GraphComponent implements OnDestroy, OnChanges { const input = inputsMap.get(__to); if (input && output) { - await editor.addConnection(new ReteConnection(arc, output, input)); + await editor.addConnection( + new ReteConnection(arc, output, input) + ); } } @@ -381,11 +417,18 @@ export class GraphComponent implements OnDestroy, OnChanges { // TODO: Test cyclic here, before continuing return ( actions.arc - .create({ __from: sourceOutput.output._id, __to: targetInput.input._id }) + .create({ + __from: sourceOutput.output._id, + __to: targetInput.input._id + }) .then(async arc => { // FIXME: return a modified context (library does not keep the custom `ReteConnection`) await this.rete?.editor.addConnection( - new ReteConnection(arc, sourceOutput, targetInput) + new ReteConnection( + arc, + sourceOutput, + targetInput + ) ); return undefined; diff --git a/apps/frontend/src/app/graph/components/node-preview/graph-node-preview.component.ts b/apps/frontend/src/app/graph/components/node-preview/graph-node-preview.component.ts index a0c97fac..c3035787 100644 --- a/apps/frontend/src/app/graph/components/node-preview/graph-node-preview.component.ts +++ b/apps/frontend/src/app/graph/components/node-preview/graph-node-preview.component.ts @@ -26,7 +26,11 @@ export class GraphNodePreviewComponent { public set node(node: NodeJSON) { this._node = { ...node, - kind: { __graph: 0, position: { x: 10, y: 10 }, type: NodeKindType.VERTEX } + kind: { + __graph: 0, + position: { x: 10, y: 10 }, + type: NodeKindType.VERTEX + } }; } } diff --git a/apps/frontend/src/app/graph/rete/node/rete.node.component.html b/apps/frontend/src/app/graph/rete/node/rete.node.component.html index a49e4f86..0444cd4a 100644 --- a/apps/frontend/src/app/graph/rete/node/rete.node.component.html +++ b/apps/frontend/src/app/graph/rete/node/rete.node.component.html @@ -44,7 +44,10 @@ [emit]="emit" > -
+
{{ input.value?.label }}
@@ -53,6 +56,8 @@ refComponent [data]="{ type: 'control', payload: input.value?.control }" [emit]="emit" - [style.display]="input.value?.control && input.value?.showControl ? '' : 'none'" + [style.display]=" + input.value?.control && input.value?.showControl ? '' : 'none' + " >
diff --git a/apps/frontend/src/app/graph/rete/node/rete.node.component.stories.ts b/apps/frontend/src/app/graph/rete/node/rete.node.component.stories.ts index eeb312e4..5f27dc2b 100644 --- a/apps/frontend/src/app/graph/rete/node/rete.node.component.stories.ts +++ b/apps/frontend/src/app/graph/rete/node/rete.node.component.stories.ts @@ -5,7 +5,9 @@ import { ReteNode } from "~/lib/ng/lib/rete"; import { ReteNodeComponent } from "./rete.node.component"; -const { nodes } = JSON.parse(JSON.stringify(BASE_SEED.graph)) as Jsonify; +const { nodes } = JSON.parse(JSON.stringify(BASE_SEED.graph)) as Jsonify< + typeof BASE_SEED.graph +>; const nodeCode = nodes[5]; const nodeVariable = nodes[8]; diff --git a/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.stories.ts b/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.stories.ts index 4bf2d429..54823d38 100644 --- a/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.stories.ts +++ b/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.stories.ts @@ -15,4 +15,6 @@ const { graph: { graphs } } = jsonify(BASE_SEED); -export const Primary: Story = { args: { graphId: graphs[0]._id, readonly: false } }; +export const Primary: Story = { + args: { graphId: graphs[0]._id, readonly: false } +}; diff --git a/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.ts b/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.ts index 7716cd6b..9dcf3928 100644 --- a/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.ts +++ b/apps/frontend/src/app/graph/views/graph-editor/graph-editor.view.ts @@ -12,7 +12,11 @@ import { GraphEditorComponent, GraphEditorNodeToAdd } from "../../components/editor/graph-editor.component"; -import { GraphActions, GraphData, NodeMoved } from "../../components/graph/graph.component"; +import { + GraphActions, + GraphData, + NodeMoved +} from "../../components/graph/graph.component"; /** * "Standalone" component for the [graph-editor]{@link GraphEditorComponent}. @@ -38,8 +42,12 @@ export class GraphEditorView implements OnInit { /** The data of the graph */ protected readonly data$ = new RequestStateSubject(async () => { const graph = await this.graphApi.findById(this.graphId); - const { data: arcs } = await this.graphApi.forArcs(graph._id).findAndCount(); - const { data: nodes } = await this.graphApi.forNodes(graph._id).findAndCount(); + const { data: arcs } = await this.graphApi + .forArcs(graph._id) + .findAndCount(); + const { data: nodes } = await this.graphApi + .forNodes(graph._id) + .findAndCount(); return { arcs, nodes } satisfies GraphData; }); @@ -54,7 +62,8 @@ export class GraphEditorView implements OnInit { protected readonly graphActions = { arc: { create: arc => this.graphApi.forArcs(this.graphId).create(arc), - remove: arc => this.graphApi.forArcs(this.graphId).delete(arc._id).then() + remove: arc => + this.graphApi.forArcs(this.graphId).delete(arc._id).then() } } as const satisfies GraphActions; @@ -90,7 +99,10 @@ export class GraphEditorView implements OnInit { this.graphApi .forNodes(this.graphId) .create({ - behavior: { __node: node._id, type: NodeBehaviorType.REFERENCE }, + behavior: { + __node: node._id, + type: NodeBehaviorType.REFERENCE + }, kind: { position }, name: `${node.name} (reference)` }) diff --git a/apps/frontend/src/app/node/components/node.list/node.list.component.html b/apps/frontend/src/app/node/components/node.list/node.list.component.html index 1a12980d..e79b37c7 100644 --- a/apps/frontend/src/app/node/components/node.list/node.list.component.html +++ b/apps/frontend/src/app/node/components/node.list/node.list.component.html @@ -10,7 +10,9 @@ entities.entity.id - {{ row._id }} + {{ + row._id + }} @@ -24,7 +26,9 @@ entities.node.name - {{ row.name }} + {{ + row.name + }} @@ -38,7 +42,9 @@ entities.node.behavior.__meta.singular - {{ row.behavior.type }} + {{ + row.behavior.type + }} @@ -53,7 +59,11 @@ - + check cancel @@ -97,7 +107,9 @@ type="button" (click)="$event.stopPropagation(); handleExpansion(row)" > - expand_more + expand_more @@ -106,7 +118,9 @@ diff --git a/apps/frontend/src/app/node/components/node.list/node.list.component.spec.ts b/apps/frontend/src/app/node/components/node.list/node.list.component.spec.ts index acd4f6da..35d9758d 100644 --- a/apps/frontend/src/app/node/components/node.list/node.list.component.spec.ts +++ b/apps/frontend/src/app/node/components/node.list/node.list.component.spec.ts @@ -37,7 +37,10 @@ describe("NodeListComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NodeListComponent, TranslateTestingModule.withTranslations({})] + imports: [ + NodeListComponent, + TranslateTestingModule.withTranslations({}) + ] }).compileComponents(); fixture = TestBed.createComponent(NodeListComponent); diff --git a/apps/frontend/src/app/node/components/node.list/node.list.component.ts b/apps/frontend/src/app/node/components/node.list/node.list.component.ts index abf2dc0c..9e7129c4 100644 --- a/apps/frontend/src/app/node/components/node.list/node.list.component.ts +++ b/apps/frontend/src/app/node/components/node.list/node.list.component.ts @@ -1,7 +1,14 @@ import { animate, style, transition, trigger } from "@angular/animations"; import { CommonModule } from "@angular/common"; import { HttpErrorResponse } from "@angular/common/http"; -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges +} from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatIconModule } from "@angular/material/icon"; import { MatTableModule } from "@angular/material/table"; @@ -9,7 +16,11 @@ import { RouterLink } from "@angular/router"; import { TranslateModule } from "@ngx-translate/core"; import { map, Observable } from "rxjs"; import { NodeJSON } from "~/lib/common/app/node/endpoints"; -import { EntityFindQuery, EntityFindResult, EntityOrder } from "~/lib/common/endpoints"; +import { + EntityFindQuery, + EntityFindResult, + EntityOrder +} from "~/lib/common/endpoints"; import { DotPath } from "~/lib/common/types"; import { MatCellDefDirective } from "~/lib/ng/lib/directives"; import { ListTableHeaderComponent } from "~/lib/ng/lib/mat-list/components/list-table-header/list-table-header.component"; @@ -46,7 +57,8 @@ export const NODE_LIST_COLUMNS = [ /** Type of the node-list columns */ export type NodeListColumn = (typeof NODE_LIST_COLUMNS)[number]; -export type NodeListColumnSortable = (typeof NODE_LIST_COLUMNS_SORTABLE)[number]; +export type NodeListColumnSortable = + (typeof NODE_LIST_COLUMNS_SORTABLE)[number]; /** The sort type for a NodeList */ export type NodeListSort = ListSortColumns; @@ -65,7 +77,10 @@ const getAnimation = () => { const closed = style({ height: 0, opacity: 0 }); const timing = "150ms"; return trigger("previewExpansion", [ - transition(":enter", [closed, animate(timing, style({ height: "*", opacity: 1 }))]), + transition(":enter", [ + closed, + animate(timing, style({ height: "*", opacity: 1 })) + ]), transition(":leave", [animate(timing, closed)]) ]); }; @@ -90,20 +105,24 @@ const getAnimation = () => { ] }) export class NodeListComponent implements OnChanges { - public static listQueryToApiQuery(query: NodeListQuery): EntityFindQuery { + public static listQueryToApiQuery( + query: NodeListQuery + ): EntityFindQuery { const { sort = new ListSortColumns() } = query; return { - order: sort.columns.map>(({ column, direction }) => { - switch (column) { - case "behavior.type": - return { behavior: { type: direction } }; - case "kind.active": - return { kind: { active: direction } }; - } + order: sort.columns.map>( + ({ column, direction }) => { + switch (column) { + case "behavior.type": + return { behavior: { type: direction } }; + case "kind.active": + return { kind: { active: direction } }; + } - return { [column]: direction }; - }) + return { [column]: direction }; + } + ) }; } // TODO: loading/error state @@ -183,7 +202,8 @@ export class NodeListComponent implements OnChanges { /** * Updates a table header direction */ - protected readonly nextDirection = ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); + protected readonly nextDirection = + ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); /** * The dataSource for the table @@ -195,14 +215,17 @@ export class NodeListComponent implements OnChanges { */ protected get isExpansionEnabled() { return ( - this.COLUMNS_ADDITIONAL.includes("actions.expansion") || this.onRowClick === "expansion" + this.COLUMNS_ADDITIONAL.includes("actions.expansion") || + this.onRowClick === "expansion" ); } /** @inheritDoc */ public ngOnChanges(changes: SimpleChanges) { if (("state$" satisfies keyof this) in changes) { - this.dataSource$ = this.state$.pipe(map(({ snapshot: { data } }) => data?.data ?? [])); + this.dataSource$ = this.state$.pipe( + map(({ snapshot: { data } }) => data?.data ?? []) + ); } } diff --git a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.html b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.html index 53eae20f..f9371f47 100644 --- a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.html +++ b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.html @@ -16,11 +16,15 @@

dialogs.node-create.title

close - {{ form.controls.name.errors | translateControlError }} + {{ + form.controls.name.errors | translateControlError + }} - entities.node.behavior.__meta.singular + entities.node.behavior.__meta.singular {{ type }} @@ -48,5 +52,7 @@

dialogs.node-create.title

- +
diff --git a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.spec.ts b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.spec.ts index 1a7a0f9e..9ed0836a 100644 --- a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.spec.ts +++ b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.spec.ts @@ -14,7 +14,9 @@ describe("NodeCreateDialog", () => { }) .overrideProvider(MAT_DIALOG_DATA, { useValue: { - initialData: { kind: { active: true, type: NodeKindType.TEMPLATE } } + initialData: { + kind: { active: true, type: NodeKindType.TEMPLATE } + } } satisfies NodeCreateDialogData }) .overrideProvider(MatDialogRef, { useValue: {} }) diff --git a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.stories.ts b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.stories.ts index 455277e8..a09f2de1 100644 --- a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.stories.ts +++ b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.stories.ts @@ -20,7 +20,9 @@ export const Primary: Story = { { provide: MAT_DIALOG_DATA, useValue: { - initialData: { kind: { active: false, type: NodeKindType.TEMPLATE } } + initialData: { + kind: { active: false, type: NodeKindType.TEMPLATE } + } } satisfies NodeCreateDialogData }, { provide: MatDialogRef, useValue: {} } diff --git a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.ts b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.ts index f0d91a47..e8af8421 100644 --- a/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.ts +++ b/apps/frontend/src/app/node/dialogs/node-create/node-create.dialog.ts @@ -18,7 +18,10 @@ import { import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; import { MatSelectModule } from "@angular/material/select"; -import type { NODE_NAME_MIN_LENGTH, NodeCreateDto } from "~/lib/common/app/node/dtos"; +import type { + NODE_NAME_MIN_LENGTH, + NodeCreateDto +} from "~/lib/common/app/node/dtos"; import { NODE_BEHAVIOR_TYPES, NodeBehaviorType @@ -46,7 +49,8 @@ export interface NodeCreateDialogData { * * Note!: only the type of behavior is taken */ - initialData: Partial> & Pick; + initialData: Partial> & + Pick; } /** * The possible (when not canceled) results from the dialog @@ -84,19 +88,20 @@ export class NodeCreateDialog { // - Create `trigger` and subtypes /** Default behavior types when creating a node template */ - public static readonly NODE_TEMPLATE_BEHAVIOR_TYPES = NODE_BEHAVIOR_TYPES.filter( - ( - type - ): type is Exclude< - NodeBehaviorType, - | NodeBehaviorType.PARAMETER_IN - | NodeBehaviorType.PARAMETER_OUT - | NodeBehaviorType.REFERENCE - > => - type !== NodeBehaviorType.PARAMETER_IN && - type !== NodeBehaviorType.PARAMETER_OUT && - type !== NodeBehaviorType.REFERENCE - ); + public static readonly NODE_TEMPLATE_BEHAVIOR_TYPES = + NODE_BEHAVIOR_TYPES.filter( + ( + type + ): type is Exclude< + NodeBehaviorType, + | NodeBehaviorType.PARAMETER_IN + | NodeBehaviorType.PARAMETER_OUT + | NodeBehaviorType.REFERENCE + > => + type !== NodeBehaviorType.PARAMETER_IN && + type !== NodeBehaviorType.PARAMETER_OUT && + type !== NodeBehaviorType.REFERENCE + ); /** * Opens this dialog @@ -111,28 +116,31 @@ export class NodeCreateDialog { data?: NodeCreateDialogData, config?: Omit ) { - return matDialog.open( + return matDialog.open< NodeCreateDialog, - { - // Some default values - maxWidth: "600px", - minWidth: "350px", - width: "40%", - - ...config, - data - } - ); + NodeCreateDialogData, + NodeCreateDialogResult + >(NodeCreateDialog, { + // Some default values + maxWidth: "600px", + minWidth: "350px", + width: "40%", + + ...config, + data + }); } - protected readonly requestCreate$ = new RequestStateSubject((body: NodeCreateDto) => - this.nodeApi.create(body) + protected readonly requestCreate$ = new RequestStateSubject( + (body: NodeCreateDto) => this.nodeApi.create(body) ); /** Creating form */ protected readonly form: FormGroup< FormControlsFrom> & { - behavior: FormControl>; + behavior: FormControl< + Exclude + >; } >; protected readonly BEHAVIOR_TYPES: ReadonlyArray< @@ -150,7 +158,10 @@ export class NodeCreateDialog { */ public constructor( @Inject(MAT_DIALOG_DATA) dialogData: NodeCreateDialogData, - private readonly matDialogRef: MatDialogRef, + private readonly matDialogRef: MatDialogRef< + NodeCreateDialog, + NodeCreateDialogResult + >, private readonly nodeApi: NodeApiService ) { const { @@ -159,14 +170,19 @@ export class NodeCreateDialog { } = dialogData; this.BEHAVIOR_TYPES = behaviorTypes.filter( - (type): type is Exclude => + ( + type + ): type is Exclude => type !== NodeBehaviorType.REFERENCE ); this.kind = kind; this.form = new FormGroup({ behavior: new FormControl( - (behavior?.type ?? null) as Exclude, + (behavior?.type ?? null) as Exclude< + NodeBehaviorType, + NodeBehaviorType.REFERENCE + >, { nonNullable: true, validators: [ @@ -174,7 +190,11 @@ export class NodeCreateDialog { control => behaviorTypes.includes(control.value as never) ? null - : { "invalid-type": { type: control.value as never } } + : { + "invalid-type": { + type: control.value as never + } + } ] } ), @@ -182,7 +202,9 @@ export class NodeCreateDialog { nonNullable: true, validators: [ Validators.required, - Validators.minLength(2 satisfies typeof NODE_NAME_MIN_LENGTH) + Validators.minLength( + 2 satisfies typeof NODE_NAME_MIN_LENGTH + ) ] }) }); @@ -215,7 +237,10 @@ export class NodeCreateDialog { case NodeBehaviorType.PARAMETER_OUT: return { type }; case NodeBehaviorType.TRIGGER: - return { trigger: { cron: "* * * * 5", type: NodeTriggerType.CRON }, type }; + return { + trigger: { cron: "* * * * 5", type: NodeTriggerType.CRON }, + type + }; case NodeBehaviorType.VARIABLE: return { type, value: "" }; } diff --git a/apps/frontend/src/app/node/views/node/node.view.ts b/apps/frontend/src/app/node/views/node/node.view.ts index 9e5c902c..5a96a5a5 100644 --- a/apps/frontend/src/app/node/views/node/node.view.ts +++ b/apps/frontend/src/app/node/views/node/node.view.ts @@ -11,8 +11,8 @@ import { RequestStateSubject } from "~/lib/ng/lib/request-state"; imports: [] }) export class NodeView { - protected readonly requestState$ = new RequestStateSubject((nodeId: EntityId) => - this.apiService.findById(nodeId) + protected readonly requestState$ = new RequestStateSubject( + (nodeId: EntityId) => this.apiService.findById(nodeId) ); @Input({ required: true, transform: (query: string) => +query }) diff --git a/apps/frontend/src/app/node/views/nodes/nodes.view.ts b/apps/frontend/src/app/node/views/nodes/nodes.view.ts index 8192805f..244f1fa1 100644 --- a/apps/frontend/src/app/node/views/nodes/nodes.view.ts +++ b/apps/frontend/src/app/node/views/nodes/nodes.view.ts @@ -47,10 +47,15 @@ type NodesViewQueryParam = NodesViewQueryParamSort; }) export class NodesView implements OnInit, OnDestroy { protected readonly nodesState$ = new RequestStateSubject( - ({ where = {}, ...query }: Parameters[0] = {}) => + ({ + where = {}, + ...query + }: Parameters[0] = {}) => this.nodeApi.findAndCount({ ...query, - where: { $and: [{ kind: { type: NodeKindType.TEMPLATE } }, where] } + where: { + $and: [{ kind: { type: NodeKindType.TEMPLATE } }, where] + } }) ); @@ -91,7 +96,10 @@ export class NodesView implements OnInit, OnDestroy { this.INTERNAL_NAVIGATION ) ) - .subscribe(params => void this.doRequest(this.queryParamsToListQuery(params))) + .subscribe( + params => + void this.doRequest(this.queryParamsToListQuery(params)) + ) ); } @@ -106,12 +114,16 @@ export class NodesView implements OnInit, OnDestroy { } protected async openCreateDialog() { - const { NodeCreateDialog } = await import("../../dialogs/node-create/node-create.dialog"); + const { NodeCreateDialog } = await import( + "../../dialogs/node-create/node-create.dialog" + ); await lastValueFrom( NodeCreateDialog.open(this.matDialog, { behaviorTypes: NodeCreateDialog.NODE_TEMPLATE_BEHAVIOR_TYPES, - initialData: { kind: { active: false, type: NodeKindType.TEMPLATE } } + initialData: { + kind: { active: false, type: NodeKindType.TEMPLATE } + } }).afterClosed() ).then(result => { if (!result) { @@ -147,21 +159,35 @@ export class NodesView implements OnInit, OnDestroy { } // TODO: remove it from this component (lib?) - private queryParamsToListQuery(queryParams: NodesViewQueryParam): NodeListQuery { + private queryParamsToListQuery( + queryParams: NodesViewQueryParam + ): NodeListQuery { // TODO: use a lib (dot-object like) const sortQP = Object.entries(queryParams) - .filter(([key, value]) => key.startsWith(SORT_PARAM_SUFFIX) && isOrderValue(value)) + .filter( + ([key, value]) => + key.startsWith(SORT_PARAM_SUFFIX) && isOrderValue(value) + ) .map(([key, value]) => [key.slice(SORT_PARAM_SUFFIX.length), value]) - .filter((element): element is [NodeListColumnSortable, ListSortOrderValueDefault] => - NODE_LIST_COLUMNS_SORTABLE.includes(element[0] as never) + .filter( + ( + element + ): element is [ + NodeListColumnSortable, + ListSortOrderValueDefault + ] => NODE_LIST_COLUMNS_SORTABLE.includes(element[0] as never) ); return { - sort: new ListSortColumns(sortQP.map(([column, direction]) => ({ column, direction }))) + sort: new ListSortColumns( + sortQP.map(([column, direction]) => ({ column, direction })) + ) }; } - private listQueryToQueryParams(listQuery: NodeListQuery): NodesViewQueryParam { + private listQueryToQueryParams( + listQuery: NodeListQuery + ): NodesViewQueryParam { // TODO: use a lib (dot-object like) const { sort = new ListSortColumns() } = listQuery; diff --git a/apps/frontend/src/app/user/user.service.spec.ts b/apps/frontend/src/app/user/user.service.spec.ts index 3b7d1216..77626bfe 100644 --- a/apps/frontend/src/app/user/user.service.spec.ts +++ b/apps/frontend/src/app/user/user.service.spec.ts @@ -22,9 +22,15 @@ describe("UserService", () => { } = BASE_SEED; expect(service.displayName(user1)).toBe(user1.email); - expect(service.displayName(user2)).toBe(`${user2.firstname} ${user2.lastname}`); + expect(service.displayName(user2)).toBe( + `${user2.firstname} ${user2.lastname}` + ); - expect(service.displayName({ ...user2, lastname: null })).toBe(user2.firstname); - expect(service.displayName({ ...user2, firstname: null })).toBe(user2.lastname); + expect(service.displayName({ ...user2, lastname: null })).toBe( + user2.firstname + ); + expect(service.displayName({ ...user2, firstname: null })).toBe( + user2.lastname + ); }); }); diff --git a/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.html b/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.html index 79a41dc6..aa53a063 100644 --- a/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.html +++ b/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.html @@ -1,14 +1,25 @@ - entities.workflow.__meta.singular + entities.workflow.__meta.singular - + entities.workflow.name - - {{ form.controls.name.errors | translateControlError }} + + {{ + form.controls.name.errors | translateControlError + }}
@@ -28,7 +39,10 @@ - + error_outline {{ error.status | translateHttpError }} diff --git a/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.ts b/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.ts index 02f41b6c..35155033 100644 --- a/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.ts +++ b/apps/frontend/src/app/workflow/components/workflow-update/workflow-update.card.ts @@ -54,9 +54,14 @@ export class WorkflowUpdateCard { public readonly update = new EventEmitter(); /** Form to update a workflow */ - protected readonly form = new FormGroup>({ + protected readonly form = new FormGroup< + FormControlsFrom + >({ active: new FormControl(false, { nonNullable: true }), - name: new FormControl("", { nonNullable: true, validators: [Validators.required] }) + name: new FormControl("", { + nonNullable: true, + validators: [Validators.required] + }) }); /** The workflow to manage in this card */ diff --git a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.html b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.html index 6f738e43..e7a75eed 100644 --- a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.html +++ b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.html @@ -10,7 +10,9 @@ entities.entity.id - {{ row._id }} + {{ + row._id + }} @@ -41,7 +43,9 @@ entities.workflow.name - {{ row.name }} + {{ + row.name + }} diff --git a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.spec.ts b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.spec.ts index 3bd7216c..4d201ed3 100644 --- a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.spec.ts +++ b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.spec.ts @@ -34,7 +34,10 @@ describe("WorkflowListComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [WorkflowListComponent, TranslateTestingModule.withTranslations({})] + imports: [ + WorkflowListComponent, + TranslateTestingModule.withTranslations({}) + ] }).compileComponents(); fixture = TestBed.createComponent(WorkflowListComponent); diff --git a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.stories.ts b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.stories.ts index ed16d298..4e636080 100644 --- a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.stories.ts +++ b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.stories.ts @@ -29,7 +29,10 @@ const { workflows } = db; const getRequestState = ( state: RequestState> -): RequestStateWithSnapshot, HttpErrorResponse> => { +): RequestStateWithSnapshot< + EntityFindResult, + HttpErrorResponse +> => { return { ...state, snapshot: getRequestStateSnapshot(state) }; }; @@ -61,4 +64,6 @@ export const Primary: Story = { }; export const Clean: Story = { args: { state$ } }; -export const ChangeColumns: Story = { args: { columns: ["name", "active", "_id"], state$ } }; +export const ChangeColumns: Story = { + args: { columns: ["name", "active", "_id"], state$ } +}; diff --git a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.ts b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.ts index 635a48f0..066c5136 100644 --- a/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.ts +++ b/apps/frontend/src/app/workflow/components/workflow.list/workflow.list.component.ts @@ -1,6 +1,13 @@ import { CommonModule } from "@angular/common"; import { HttpErrorResponse } from "@angular/common/http"; -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges +} from "@angular/core"; import { MatBadgeModule } from "@angular/material/badge"; import { MatIconModule } from "@angular/material/icon"; import { MatTableModule } from "@angular/material/table"; @@ -60,10 +67,16 @@ export interface WorkflowListQuery { ] }) export class WorkflowListComponent implements OnChanges { - public static listQueryToApiQuery(query: WorkflowListQuery): EntityFindQuery { + public static listQueryToApiQuery( + query: WorkflowListQuery + ): EntityFindQuery { const { sort = new ListSortColumns() } = query; - return { order: sort.columns.map(({ column, direction }) => ({ [column]: direction })) }; + return { + order: sort.columns.map(({ column, direction }) => ({ + [column]: direction + })) + }; } // TODO: loading/error state @@ -73,7 +86,10 @@ export class WorkflowListComponent implements OnChanges { */ @Input({ required: true }) public state$!: Observable< - RequestStateWithSnapshot, HttpErrorResponse> + RequestStateWithSnapshot< + EntityFindResult, + HttpErrorResponse + > >; /** @@ -82,7 +98,8 @@ export class WorkflowListComponent implements OnChanges { * @default All columns */ @Input() - public columns: readonly WorkflowListColumn[] = WORKFLOW_LIST_COLUMNS.slice(); + public columns: readonly WorkflowListColumn[] = + WORKFLOW_LIST_COLUMNS.slice(); @Input() public rowUrl?: (workflow: WorkflowJSON) => string; @@ -118,12 +135,15 @@ export class WorkflowListComponent implements OnChanges { /** * Updates a table header direction */ - protected readonly nextDirection = ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); + protected readonly nextDirection = + ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); /** @inheritDoc */ public ngOnChanges(changes: SimpleChanges) { if (("state$" satisfies keyof this) in changes) { - this.dataSource$ = this.state$.pipe(map(({ snapshot: { data } }) => data?.data ?? [])); + this.dataSource$ = this.state$.pipe( + map(({ snapshot: { data } }) => data?.data ?? []) + ); } } diff --git a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.html b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.html index 160b6b2b..3f20ed88 100644 --- a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.html +++ b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.html @@ -16,7 +16,9 @@

dialogs.workflow-create.title

close - {{ form.controls.name.errors | translateControlError }} + {{ + form.controls.name.errors | translateControlError + }}
@@ -69,5 +71,7 @@

dialogs.workflow-create.title

- +
diff --git a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.spec.ts b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.spec.ts index 12fd51a4..1c756899 100644 --- a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.spec.ts +++ b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.spec.ts @@ -1,7 +1,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; -import { WorkflowCreateDialog, WorkflowCreateDialogData } from "./workflow-create.dialog"; +import { + WorkflowCreateDialog, + WorkflowCreateDialogData +} from "./workflow-create.dialog"; describe("WorkflowCreateDialog", () => { let component: WorkflowCreateDialog; @@ -11,7 +14,9 @@ describe("WorkflowCreateDialog", () => { await TestBed.configureTestingModule({ imports: [WorkflowCreateDialog] }) - .overrideProvider(MAT_DIALOG_DATA, { useValue: {} satisfies WorkflowCreateDialogData }) + .overrideProvider(MAT_DIALOG_DATA, { + useValue: {} satisfies WorkflowCreateDialogData + }) .overrideProvider(MatDialogRef, { useValue: {} }) .compileComponents(); diff --git a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.stories.ts b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.stories.ts index 5037aa7f..9e6daea6 100644 --- a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.stories.ts +++ b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.stories.ts @@ -2,7 +2,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; import type { Meta, StoryObj } from "@storybook/angular"; import { moduleMetadata } from "@storybook/angular"; -import { WorkflowCreateDialog, WorkflowCreateDialogData } from "./workflow-create.dialog"; +import { + WorkflowCreateDialog, + WorkflowCreateDialogData +} from "./workflow-create.dialog"; const meta: Meta = { component: WorkflowCreateDialog, @@ -16,7 +19,10 @@ export const Primary: Story = { decorators: [ moduleMetadata({ providers: [ - { provide: MAT_DIALOG_DATA, useValue: {} satisfies WorkflowCreateDialogData }, + { + provide: MAT_DIALOG_DATA, + useValue: {} satisfies WorkflowCreateDialogData + }, { provide: MatDialogRef, useValue: {} } ] }) diff --git a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.ts b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.ts index 3c6b7ae0..05ed3faf 100644 --- a/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.ts +++ b/apps/frontend/src/app/workflow/dialogs/workflow-create/workflow-create.dialog.ts @@ -33,12 +33,18 @@ import { take, tap } from "rxjs"; -import type { WORKFLOW_NAME_MIN_LENGTH, WorkflowCreateDto } from "~/lib/common/app/workflow/dtos"; +import type { + WORKFLOW_NAME_MIN_LENGTH, + WorkflowCreateDto +} from "~/lib/common/app/workflow/dtos"; import { WorkflowJSON } from "~/lib/common/app/workflow/endpoints"; import { ApiModule } from "~/lib/ng/lib/api"; import { WorkflowApiService } from "~/lib/ng/lib/api/workflow-api"; import { FormControlsFrom } from "~/lib/ng/lib/forms"; -import { RequestStateSubject, RequestStateWithSnapshot } from "~/lib/ng/lib/request-state"; +import { + RequestStateSubject, + RequestStateWithSnapshot +} from "~/lib/ng/lib/request-state"; import { TranslationModule } from "~/lib/ng/lib/translation"; /** @@ -60,16 +66,22 @@ export interface WorkflowCreateDialogResult { created: WorkflowJSON; } -interface WorkflowUniqueStateBase { +interface WorkflowUniqueStateBase< + T extends "ignore" | "verified" | "verifying" +> { type: T; } -interface WorkflowUniqueStatePreload extends WorkflowUniqueStateBase<"ignore" | "verifying"> { +interface WorkflowUniqueStatePreload + extends WorkflowUniqueStateBase<"ignore" | "verifying"> { input: string; } -interface WorkflowUniqueStateVerified extends WorkflowUniqueStateBase<"verified"> { +interface WorkflowUniqueStateVerified + extends WorkflowUniqueStateBase<"verified"> { state: RequestStateWithSnapshot; } -type WorkflowUniqueState = WorkflowUniqueStatePreload | WorkflowUniqueStateVerified; +type WorkflowUniqueState = + | WorkflowUniqueStatePreload + | WorkflowUniqueStateVerified; @Component({ standalone: true, @@ -119,15 +131,16 @@ export class WorkflowCreateDialog { }); } - protected readonly requestCreate$ = new RequestStateSubject((body: WorkflowCreateDto) => - this.workflowApi.create(body) + protected readonly requestCreate$ = new RequestStateSubject( + (body: WorkflowCreateDto) => this.workflowApi.create(body) ); /** Creating form */ protected readonly form: FormGroup>; protected readonly unique$: Observable; - private readonly NAME_MIN_LENGTH = 2 satisfies typeof WORKFLOW_NAME_MIN_LENGTH; + private readonly NAME_MIN_LENGTH = + 2 satisfies typeof WORKFLOW_NAME_MIN_LENGTH; /** * Constructor with "dependency injection" @@ -137,7 +150,8 @@ export class WorkflowCreateDialog { * @param workflowApi injected */ public constructor( - @Inject(MAT_DIALOG_DATA) dialogData: WorkflowCreateDialogData | undefined, + @Inject(MAT_DIALOG_DATA) + dialogData: WorkflowCreateDialogData | undefined, private readonly matDialogRef: MatDialogRef< WorkflowCreateDialog, WorkflowCreateDialogResult @@ -158,7 +172,8 @@ export class WorkflowCreateDialog { take(1), map(({ state }) => { if ( - (state.state === "success" && !state.data) || + (state.state === "success" && + !state.data) || state.state === "failed" ) { return { @@ -171,7 +186,10 @@ export class WorkflowCreateDialog { ) ], nonNullable: true, - validators: [Validators.required, Validators.minLength(this.NAME_MIN_LENGTH)] + validators: [ + Validators.required, + Validators.minLength(this.NAME_MIN_LENGTH) + ] }) }); @@ -187,7 +205,10 @@ export class WorkflowCreateDialog { input => ({ input, - type: input.length > this.NAME_MIN_LENGTH ? "verifying" : "ignore" + type: + this.NAME_MIN_LENGTH < input.length + ? "verifying" + : "ignore" }) satisfies WorkflowUniqueStatePreload ) ); @@ -197,11 +218,20 @@ export class WorkflowCreateDialog { debounceTime(500), tap(({ input }) => void requestState$.request(input)), switchMap(() => requestState$), - map(state => ({ state, type: "verified" }) satisfies WorkflowUniqueStateVerified) + map( + state => + ({ + state, + type: "verified" + }) satisfies WorkflowUniqueStateVerified + ) ); this.unique$ = merge( - of({ input: "", type: "ignore" } satisfies WorkflowUniqueStatePreload), + of({ + input: "", + type: "ignore" + } satisfies WorkflowUniqueStatePreload), name$, request$ ).pipe(distinctUntilChanged()); diff --git a/apps/frontend/src/app/workflow/views/workflow.routes.ts b/apps/frontend/src/app/workflow/views/workflow.routes.ts index 370bd844..b901053e 100644 --- a/apps/frontend/src/app/workflow/views/workflow.routes.ts +++ b/apps/frontend/src/app/workflow/views/workflow.routes.ts @@ -10,7 +10,9 @@ export const WORKFLOW_ROUTES: Routes = [ { // This component is bigger loadChildren: () => - import("./workflow/workflow.view.routing").then(m => m.WorkflowViewRouting), + import("./workflow/workflow.view.routing").then( + m => m.WorkflowViewRouting + ), path: ":workflowId" } ]; diff --git a/apps/frontend/src/app/workflow/views/workflow/workflow.view.html b/apps/frontend/src/app/workflow/views/workflow/workflow.view.html index 127f0719..21b6a9c4 100644 --- a/apps/frontend/src/app/workflow/views/workflow/workflow.view.html +++ b/apps/frontend/src/app/workflow/views/workflow/workflow.view.html @@ -1,18 +1,33 @@ - + - + {{ workflow.name }} - + error errors.occurred - + @@ -24,7 +39,9 @@ > @@ -51,9 +68,17 @@ - + - diff --git a/apps/frontend/src/app/workflow/views/workflow/workflow.view.ts b/apps/frontend/src/app/workflow/views/workflow/workflow.view.ts index 3d6bbf01..207bac2e 100644 --- a/apps/frontend/src/app/workflow/views/workflow/workflow.view.ts +++ b/apps/frontend/src/app/workflow/views/workflow/workflow.view.ts @@ -52,14 +52,16 @@ export interface WorkflowViewRouteData { }) export class WorkflowView implements OnInit, OnDestroy { /** RSS for the loading workflow */ - protected readonly requestState$ = new RequestStateSubject((workflowId: EntityId) => - this.workflowApi.findById(workflowId) + protected readonly requestState$ = new RequestStateSubject( + (workflowId: EntityId) => this.workflowApi.findById(workflowId) ); /** RSS for the update workflow */ protected readonly requestUpdateState$ = new RequestStateSubject( ({ _id }: WorkflowJSON, body: WorkflowUpdateDto) => - this.workflowApi.update(_id, body).then(({ _id }) => this.requestState$.request(_id)) + this.workflowApi + .update(_id, body) + .then(({ _id }) => this.requestState$.request(_id)) ); protected readonly requestState = toSignal( @@ -106,7 +108,10 @@ export class WorkflowView implements OnInit, OnDestroy { public ngOnInit() { this.subscription.add( this.activatedRoute.data.subscribe(data => { - this.matTab.selectedIndex = (data as WorkflowViewRouteData).graph ? 1 : 0; + this.matTab.selectedIndex = (data as WorkflowViewRouteData) + .graph + ? 1 + : 0; }) ); diff --git a/apps/frontend/src/app/workflow/views/workflows/workflows.view.ts b/apps/frontend/src/app/workflow/views/workflows/workflows.view.ts index 97b355e5..b1e550c8 100644 --- a/apps/frontend/src/app/workflow/views/workflows/workflows.view.ts +++ b/apps/frontend/src/app/workflow/views/workflows/workflows.view.ts @@ -85,7 +85,10 @@ export class WorkflowsView implements OnInit, OnDestroy { this.INTERNAL_NAVIGATION ) ) - .subscribe(params => void this.doRequest(this.queryParamsToListQuery(params))) + .subscribe( + params => + void this.doRequest(this.queryParamsToListQuery(params)) + ) ); } @@ -107,15 +110,15 @@ export class WorkflowsView implements OnInit, OnDestroy { "../../dialogs/workflow-create/workflow-create.dialog" ); - await lastValueFrom(WorkflowCreateDialog.open(this.matDialog).afterClosed()).then( - result => { - if (!result) { - return; - } - - void this.router.navigateByUrl(this.workflowUrl(result.created)); + await lastValueFrom( + WorkflowCreateDialog.open(this.matDialog).afterClosed() + ).then(result => { + if (!result) { + return; } - ); + + void this.router.navigateByUrl(this.workflowUrl(result.created)); + }); } /** @@ -144,21 +147,33 @@ export class WorkflowsView implements OnInit, OnDestroy { } // TODO: remove it from this component (lib?) - private queryParamsToListQuery(queryParams: WorkflowsViewQueryParam): WorkflowListQuery { + private queryParamsToListQuery( + queryParams: WorkflowsViewQueryParam + ): WorkflowListQuery { // TODO: use a lib (dot-object like) const sortQP = Object.entries(queryParams) - .filter(([key, value]) => key.startsWith(SORT_PARAM_SUFFIX) && isOrderValue(value)) + .filter( + ([key, value]) => + key.startsWith(SORT_PARAM_SUFFIX) && isOrderValue(value) + ) .map(([key, value]) => [key.slice(SORT_PARAM_SUFFIX.length), value]) - .filter((element): element is [WorkflowListColumn, ListSortOrderValueDefault] => - WORKFLOW_LIST_COLUMNS.includes(element[0] as never) + .filter( + ( + element + ): element is [WorkflowListColumn, ListSortOrderValueDefault] => + WORKFLOW_LIST_COLUMNS.includes(element[0] as never) ); return { - sort: new ListSortColumns(sortQP.map(([column, direction]) => ({ column, direction }))) + sort: new ListSortColumns( + sortQP.map(([column, direction]) => ({ column, direction })) + ) }; } - private listQueryToQueryParams(listQuery: WorkflowListQuery): WorkflowsViewQueryParam { + private listQueryToQueryParams( + listQuery: WorkflowListQuery + ): WorkflowsViewQueryParam { // TODO: use a lib (dot-object like) const { sort = new ListSortColumns() } = listQuery; diff --git a/apps/frontend/src/index.html b/apps/frontend/src/index.html index 5d1aae69..9301d9fe 100644 --- a/apps/frontend/src/index.html +++ b/apps/frontend/src/index.html @@ -13,7 +13,10 @@ href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet" /> - + diff --git a/apps/frontend/src/styles.scss b/apps/frontend/src/styles.scss index 59d0d7f2..5893870f 100644 --- a/apps/frontend/src/styles.scss +++ b/apps/frontend/src/styles.scss @@ -37,7 +37,12 @@ body { @each $breakpoint, $_ in utils.$breakpoints { @include utils.screen-breakpoint($breakpoint) { @include ng.tw-base(utils.$spacing, $colors, $breakpoint); - @include ng.tw-base(utils.$spacing, $colors, "#{$breakpoint}\\/hover", "hover"); + @include ng.tw-base( + utils.$spacing, + $colors, + "#{$breakpoint}\\/hover", + "hover" + ); } @include utils.screen-breakpoint($breakpoint, true) { diff --git a/apps/frontend/tsconfig.app.json b/apps/frontend/tsconfig.app.json index 038ef3e0..43110ed2 100644 --- a/apps/frontend/tsconfig.app.json +++ b/apps/frontend/tsconfig.app.json @@ -4,11 +4,11 @@ "types": [] }, "exclude": [ + "**/*.stories.js", + "**/*.stories.ts", "jest.config.ts", - "src/**/*.test.ts", "src/**/*.spec.ts", - "**/*.stories.ts", - "**/*.stories.js" + "src/**/*.test.ts" ], "extends": "./tsconfig.json", "files": ["src/main.ts"], diff --git a/apps/frontend/tsconfig.doc.json b/apps/frontend/tsconfig.doc.json index 53dffb47..b3160450 100644 --- a/apps/frontend/tsconfig.doc.json +++ b/apps/frontend/tsconfig.doc.json @@ -1,5 +1,9 @@ { - "exclude": ["src/dev/**/*", "**/*.stories.ts", "**/*.stories.js"], + "exclude": ["**/*.stories.js", "**/*.stories.ts", "src/dev/**/*"], "extends": "./tsconfig.app.json", - "include": ["src/**/*.ts", "../../libs/common/src/**/*.ts", "../../libs/ng/src/**/*.ts"] + "include": [ + "../../libs/common/src/**/*.ts", + "../../libs/ng/src/**/*.ts", + "src/**/*.ts" + ] } diff --git a/apps/frontend/tsconfig.spec.json b/apps/frontend/tsconfig.spec.json index ffd98040..d7f7df45 100644 --- a/apps/frontend/tsconfig.spec.json +++ b/apps/frontend/tsconfig.spec.json @@ -6,5 +6,10 @@ }, "extends": "./tsconfig.json", "files": ["test/setup.ts"], - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "jest.config.ts", + "src/**/*.d.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] } diff --git a/docker-compose.yml b/docker-compose.yml index dfd76664..901d661a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,9 @@ version: "3.4" services: db: image: postgres:16-alpine - "environment": + environment: POSTGRES_DB: "nna" POSTGRES_USER: "nna" POSTGRES_PASSWORD: M1fmx9Tef2dq5UXN - "ports": + ports: - "127.0.0.1:5432:5432" diff --git a/jest.preset.js b/jest.preset.js index dce577db..ae65d90c 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -7,7 +7,8 @@ const path = require("path"); module.exports = { ...nxPreset, coverageReporters: ["text"], - setupFilesAfterEnv: [path.join(__dirname, "./tools/jest/jest-extended.ts")], - // eslint-disable-next-line @cspell/spellchecker -- plugin names - watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"] + watchPlugins: [ + "jest-watch-typeahead/filename", + "jest-watch-typeahead/testname" + ] }; diff --git a/libs/common/.eslintrc.json b/libs/common/.eslintrc.json index 91fa324e..8903dc02 100644 --- a/libs/common/.eslintrc.json +++ b/libs/common/.eslintrc.json @@ -17,7 +17,11 @@ "patterns": [ { "allowTypeImports": true, - "group": ["@angular/*", "@nestjs/*", "!@nestjs/mapped-types"], + "group": [ + "@angular/*", + "@nestjs/*", + "!@nestjs/mapped-types" + ], "message": "Avoid import `backend` or `frontend` package." } ] diff --git a/libs/common/src/app/category/dtos/category.create.dto.ts b/libs/common/src/app/category/dtos/category.create.dto.ts index 5132bcd3..25c3fe6d 100644 --- a/libs/common/src/app/category/dtos/category.create.dto.ts +++ b/libs/common/src/app/category/dtos/category.create.dto.ts @@ -6,9 +6,9 @@ import { ENTITY_BASE_KEYS } from "../../../dtos/entity"; /** * The mandatory keys to create a [category]{@link CategoryDto}. */ -export const CATEGORY_CREATE_KEYS_MANDATORY = ["name"] as const satisfies ReadonlyArray< - keyof CategoryDto ->; +export const CATEGORY_CREATE_KEYS_MANDATORY = [ + "name" +] as const satisfies ReadonlyArray; /** * DTO used to create [category]{@link CategoryDto} @@ -16,5 +16,8 @@ export const CATEGORY_CREATE_KEYS_MANDATORY = ["name"] as const satisfies Readon */ export class CategoryCreateDto extends IntersectionType( PickType(CategoryDto, CATEGORY_CREATE_KEYS_MANDATORY), - OmitType(CategoryDto, [...ENTITY_BASE_KEYS, ...CATEGORY_CREATE_KEYS_MANDATORY]) + OmitType(CategoryDto, [ + ...ENTITY_BASE_KEYS, + ...CATEGORY_CREATE_KEYS_MANDATORY + ]) ) {} diff --git a/libs/common/src/app/category/endpoints/category.endpoint.ts b/libs/common/src/app/category/endpoints/category.endpoint.ts index f4080ef4..2e2f7ee5 100644 --- a/libs/common/src/app/category/endpoints/category.endpoint.ts +++ b/libs/common/src/app/category/endpoints/category.endpoint.ts @@ -10,5 +10,6 @@ import { CategoryCreateDto, CategoryDto, CategoryUpdateDto } from "../dtos"; export const CATEGORIES_ENDPOINT_PREFIX = "/v1/categories"; export type CategoryJSON = Jsonify; -export type CategoryEndpoint = CategoryJSON> = - EntityEndpoint; +export type CategoryEndpoint< + T extends CategoryJSON | DtoToEntity = CategoryJSON +> = EntityEndpoint; diff --git a/libs/common/src/app/graph/algorithms/graph.has-cycle.spec.ts b/libs/common/src/app/graph/algorithms/graph.has-cycle.spec.ts index abe068bf..79de3596 100644 --- a/libs/common/src/app/graph/algorithms/graph.has-cycle.spec.ts +++ b/libs/common/src/app/graph/algorithms/graph.has-cycle.spec.ts @@ -1,5 +1,8 @@ import { graphHasCycle } from "./graph.has-cycle"; -import { AdjacencyListTransformationParams, getAdjacencyList } from "../transformations"; +import { + AdjacencyListTransformationParams, + getAdjacencyList +} from "../transformations"; describe("graphHasCycle", () => { it("should not have a cycle", () => { @@ -37,7 +40,10 @@ describe("graphHasCycle", () => { ], nodes: [ { inputs: [{ _id: 10 }], outputs: [{ _id: 11 }] }, - { inputs: [{ _id: 20 }, { _id: 21 }], outputs: [{ _id: 22 }] }, + { + inputs: [{ _id: 20 }, { _id: 21 }], + outputs: [{ _id: 22 }] + }, { inputs: [{ _id: 30 }, { _id: 31 }], outputs: [] } ] }, @@ -83,7 +89,7 @@ describe("graphHasCycle", () => { ]; for (const graph of graphs) { - expect(graphHasCycle(getAdjacencyList(graph))).toBeFalse(); + expect(graphHasCycle(getAdjacencyList(graph))).toBe(false); } }); @@ -114,7 +120,10 @@ describe("graphHasCycle", () => { nodes: [ { inputs: [{ _id: 10 }], outputs: [{ _id: 11 }] }, { inputs: [{ _id: 20 }], outputs: [{ _id: 22 }] }, - { inputs: [{ _id: 30 }, { _id: 31 }], outputs: [{ _id: 32 }] } + { + inputs: [{ _id: 30 }, { _id: 31 }], + outputs: [{ _id: 32 }] + } ] }, { @@ -136,7 +145,7 @@ describe("graphHasCycle", () => { ]; for (const graph of graphs) { - expect(graphHasCycle(getAdjacencyList(graph))).toBeTrue(); + expect(graphHasCycle(getAdjacencyList(graph))).toBe(true); } }); }); diff --git a/libs/common/src/app/graph/algorithms/graph.has-cycle.ts b/libs/common/src/app/graph/algorithms/graph.has-cycle.ts index 80ee3541..3d582ec2 100644 --- a/libs/common/src/app/graph/algorithms/graph.has-cycle.ts +++ b/libs/common/src/app/graph/algorithms/graph.has-cycle.ts @@ -37,7 +37,9 @@ export function graphHasCycle(graph: AdjacencyList): boolean { filter: node => roots.has(node), getChildren: node => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Will thrown an error if the data is invalid - const children = graph.get(node)!.adjacentTo.map(({ to: { node } }) => node); + const children = graph + .get(node)! + .adjacentTo.map(({ to: { node } }) => node); for (const child of children) { // The node has already been visited, there's a cycle diff --git a/libs/common/src/app/graph/dtos/arc/graph-arc.create.dto.ts b/libs/common/src/app/graph/dtos/arc/graph-arc.create.dto.ts index 26302752..2bcc6bc3 100644 --- a/libs/common/src/app/graph/dtos/arc/graph-arc.create.dto.ts +++ b/libs/common/src/app/graph/dtos/arc/graph-arc.create.dto.ts @@ -6,14 +6,17 @@ import { ENTITY_BASE_KEYS } from "../../../../dtos/entity"; /** * The mandatory keys to create a [graph-arcs]{@link GraphArcDto}. */ -export const GRAPH_ARC_KEYS_MANDATORY = [] as const satisfies ReadonlyArray; +export const GRAPH_ARC_KEYS_MANDATORY = [] as const satisfies ReadonlyArray< + keyof GraphArcDto +>; /** * The readonly keys of a [graph-arcs]{@link GraphArcDto}. */ -export const GRAPH_ARC_KEYS_READONLY = ["from", "to"] as const satisfies ReadonlyArray< - keyof GraphArcDto ->; +export const GRAPH_ARC_KEYS_READONLY = [ + "from", + "to" +] as const satisfies ReadonlyArray; /** * DTO used to create [graph-arcs]{@link GraphArcDto}. diff --git a/libs/common/src/app/graph/dtos/node/graph-node.create.dto.ts b/libs/common/src/app/graph/dtos/node/graph-node.create.dto.ts index 2b68bfb5..4b038e01 100644 --- a/libs/common/src/app/graph/dtos/node/graph-node.create.dto.ts +++ b/libs/common/src/app/graph/dtos/node/graph-node.create.dto.ts @@ -14,7 +14,9 @@ import { NodeKindVertexDto } from "../../../node/dtos/kind"; /** * DTO to use when creating a `node-kind` to a `node` linked to a `graph` */ -export class GraphNodeKindCreateDto extends PickType(NodeKindVertexDto, ["position"]) {} +export class GraphNodeKindCreateDto extends PickType(NodeKindVertexDto, [ + "position" +]) {} /** * DTO to use when creating a `node` linked to a `graph` diff --git a/libs/common/src/app/graph/dtos/node/graph-node.update.dto.ts b/libs/common/src/app/graph/dtos/node/graph-node.update.dto.ts index e465badc..329fb67b 100644 --- a/libs/common/src/app/graph/dtos/node/graph-node.update.dto.ts +++ b/libs/common/src/app/graph/dtos/node/graph-node.update.dto.ts @@ -2,12 +2,17 @@ import { OmitType, PartialType } from "@nestjs/mapped-types"; import { Expose, Type } from "class-transformer"; import { ValidateNested } from "class-validator"; -import { GraphNodeCreateDto, GraphNodeKindCreateDto } from "./graph-node.create.dto"; +import { + GraphNodeCreateDto, + GraphNodeKindCreateDto +} from "./graph-node.create.dto"; /** * DTO to use when updating a `node-kind` to a `node` linked to a `graph` */ -export class GraphNodeKindUpdateDto extends PartialType(GraphNodeKindCreateDto) {} +export class GraphNodeKindUpdateDto extends PartialType( + GraphNodeKindCreateDto +) {} /** * DTO to use when updating a `node` linked to a `graph` diff --git a/libs/common/src/app/graph/dtos/node/graph-node.update.spec.ts b/libs/common/src/app/graph/dtos/node/graph-node.update.spec.ts index b3ca92db..373b8545 100644 --- a/libs/common/src/app/graph/dtos/node/graph-node.update.spec.ts +++ b/libs/common/src/app/graph/dtos/node/graph-node.update.spec.ts @@ -1,14 +1,23 @@ import { plainToInstance } from "class-transformer"; -import { GraphNodeKindUpdateDto, GraphNodeUpdateDto } from "./graph-node.update.dto"; +import { + GraphNodeKindUpdateDto, + GraphNodeUpdateDto +} from "./graph-node.update.dto"; import { transformOptions } from "../../../../options"; describe("GraphNodeUpdateDto", () => { describe("Transformation", () => { it("should transform with partial content", () => { - const toTransform = { kind: {} } as const satisfies GraphNodeUpdateDto; + const toTransform = { + kind: {} + } as const satisfies GraphNodeUpdateDto; - const transformed = plainToInstance(GraphNodeUpdateDto, toTransform, transformOptions); + const transformed = plainToInstance( + GraphNodeUpdateDto, + toTransform, + transformOptions + ); expect(transformed.kind).toBeInstanceOf(GraphNodeKindUpdateDto); expect(transformed.name).toBeUndefined(); diff --git a/libs/common/src/app/graph/endpoints/graph-arc.endpoint.ts b/libs/common/src/app/graph/endpoints/graph-arc.endpoint.ts index b37305e9..21c0846f 100644 --- a/libs/common/src/app/graph/endpoints/graph-arc.endpoint.ts +++ b/libs/common/src/app/graph/endpoints/graph-arc.endpoint.ts @@ -10,7 +10,10 @@ import { GraphArcCreateDto, GraphArcDto, GraphArcUpdateDto } from "../dtos/arc"; * Endpoint path parts for [arcs]{@link GraphArcDto} (without global prefix). * The parameter goes in the middle starting with a `/`. */ -export const GRAPH_ARCS_ENDPOINT_PARTS = [GRAPHS_ENDPOINT_PREFIX, "/arcs"] as const; +export const GRAPH_ARCS_ENDPOINT_PARTS = [ + GRAPHS_ENDPOINT_PREFIX, + "/arcs" +] as const; /** * Generates the endpoint for `graph-arc`s for a graph. @@ -24,5 +27,6 @@ export function generateGraphArcsEndpoint(graphId: EntityId) { } export type GraphArcJSON = Jsonify; -export type GraphArcEndpoint | GraphArcJSON = GraphArcJSON> = - EntityEndpoint; +export type GraphArcEndpoint< + T extends DtoToEntity | GraphArcJSON = GraphArcJSON +> = EntityEndpoint; diff --git a/libs/common/src/app/graph/endpoints/graph-node.endpoint.ts b/libs/common/src/app/graph/endpoints/graph-node.endpoint.ts index 563db3d6..65663280 100644 --- a/libs/common/src/app/graph/endpoints/graph-node.endpoint.ts +++ b/libs/common/src/app/graph/endpoints/graph-node.endpoint.ts @@ -11,7 +11,10 @@ import { GraphNodeCreateDto } from "../dtos/node/graph-node.create.dto"; * Endpoint path parts for [nodes]{@link NodeDto} (without global prefix). * The parameter goes in the middle starting with a `/`. */ -export const GRAPH_NODES_ENDPOINT_PARTS = [GRAPHS_ENDPOINT_PREFIX, "/nodes"] as const; +export const GRAPH_NODES_ENDPOINT_PARTS = [ + GRAPHS_ENDPOINT_PREFIX, + "/nodes" +] as const; /** * Generates the endpoint for `graph-node`s for a graph. @@ -25,5 +28,6 @@ export function generateGraphNodesEndpoint(graphId: EntityId) { } export type GraphNodeJSON = Jsonify; -export type GraphNodeEndpoint | GraphNodeJSON = GraphNodeJSON> = - EntityEndpoint; +export type GraphNodeEndpoint< + T extends DtoToEntity | GraphNodeJSON = GraphNodeJSON +> = EntityEndpoint; diff --git a/libs/common/src/app/graph/endpoints/graph.endpoint.ts b/libs/common/src/app/graph/endpoints/graph.endpoint.ts index 748617af..9a2e48f6 100644 --- a/libs/common/src/app/graph/endpoints/graph.endpoint.ts +++ b/libs/common/src/app/graph/endpoints/graph.endpoint.ts @@ -10,5 +10,6 @@ import { GraphDto } from "../dtos"; export const GRAPHS_ENDPOINT_PREFIX = "/v1/graphs"; export type GraphJSON = Jsonify; -export type GraphEndpoint | GraphJSON = GraphJSON> = - EntityReadEndpoint; +export type GraphEndpoint< + T extends DtoToEntity | GraphJSON = GraphJSON +> = EntityReadEndpoint; diff --git a/libs/common/src/app/graph/transformations/get-adjacency-list.spec.ts b/libs/common/src/app/graph/transformations/get-adjacency-list.spec.ts index d5c260be..f736db16 100644 --- a/libs/common/src/app/graph/transformations/get-adjacency-list.spec.ts +++ b/libs/common/src/app/graph/transformations/get-adjacency-list.spec.ts @@ -32,9 +32,10 @@ describe("getAdjacencyList", () => { expect(list).toHaveLength(graph.nodes.length); expect( list.every( - ({ adjacentBy, adjacentTo }) => adjacentBy.length === 0 && adjacentTo.length === 0 + ({ adjacentBy, adjacentTo }) => + adjacentBy.length === 0 && adjacentTo.length === 0 ) - ).toBeTrue(); + ).toBe(true); }); it("should return an adjacency list (simple)", () => { @@ -67,7 +68,10 @@ describe("getAdjacencyList", () => { // Node B { inputs: [{ _id: 20 }], outputs: [{ _id: 20 }, { _id: 21 }] }, // Node C - { inputs: [{ _id: 30 }, { _id: 31 }], outputs: [{ _id: 30 }, { _id: 31 }] }, + { + inputs: [{ _id: 30 }, { _id: 31 }], + outputs: [{ _id: 30 }, { _id: 31 }] + }, // Node D { inputs: [{ _id: 40 }], outputs: [{ _id: 40 }] }, // Node E @@ -98,8 +102,10 @@ describe("getAdjacencyList", () => { const [nodeA, nodeB, nodeC, nodeD, nodeE] = nodes; // As the order is not guaranteed - const sortAdjacency = ({ arc: a }: AdjacencyListLink, { arc: b }: AdjacencyListLink) => - a.__from === b.__from ? a.__to - b.__to : a.__from - b.__from; + const sortAdjacency = ( + { arc: a }: AdjacencyListLink, + { arc: b }: AdjacencyListLink + ) => (a.__from === b.__from ? a.__to - b.__to : a.__from - b.__from); { // NodeA Adjacency @@ -181,7 +187,9 @@ describe("getAdjacencyList", () => { expect(adjacentBy).toHaveLength(4); expect(adjacentTo).toHaveLength(1); - const [byC1, byC2, byD, byE] = adjacentBy.slice().sort(sortAdjacency); + const [byC1, byC2, byD, byE] = adjacentBy + .slice() + .sort(sortAdjacency); expect(byC1.arc).toBe(arcs[5]); expect(byC1.from.node).toBe(nodes[2]); expect(byC2.arc).toBe(arcs[6]); @@ -198,9 +206,9 @@ describe("getAdjacencyList", () => { }); it("should fail when an arc does not connect 2 nodes", () => { - expect(() => getAdjacencyList({ arcs: [{ __from: 2, __to: 1 }], nodes: [] })).toThrow( - AdjacencyListUnlinkedArcException - ); + expect(() => + getAdjacencyList({ arcs: [{ __from: 2, __to: 1 }], nodes: [] }) + ).toThrow(AdjacencyListUnlinkedArcException); expect(() => getAdjacencyList({ @@ -211,7 +219,12 @@ describe("getAdjacencyList", () => { { __from: 1, __to: 1 }, { __from: 2, __to: 2 } ], - nodes: [{ inputs: [{ _id: 1 }, { _id: 2 }], outputs: [{ _id: 1 }, { _id: 2 }] }] + nodes: [ + { + inputs: [{ _id: 1 }, { _id: 2 }], + outputs: [{ _id: 1 }, { _id: 2 }] + } + ] }) ).toThrow(AdjacencyListUnlinkedArcException); }); diff --git a/libs/common/src/app/graph/transformations/get-adjacency.list.ts b/libs/common/src/app/graph/transformations/get-adjacency.list.ts index 815480ca..b2420213 100644 --- a/libs/common/src/app/graph/transformations/get-adjacency.list.ts +++ b/libs/common/src/app/graph/transformations/get-adjacency.list.ts @@ -40,7 +40,6 @@ export interface AdjacencyListTransformationParams< nodes: readonly Node[]; } -/* eslint-disable no-use-before-define -- recursive type */ /** * The link between two nodes with its arc. * @@ -63,12 +62,14 @@ export interface AdjacencyListLink< */ to: AdjacencyListItem; } -/* eslint-enable */ /** * An item contains the nodes connected by and to the current node */ -export interface AdjacencyListItem { +export interface AdjacencyListItem< + Arc extends AdjacencyListArc, + Node extends AdjacencyListNode +> { /** * The adjacency from other nodes leading to this one */ @@ -100,7 +101,10 @@ export type AdjacencyList< * @param params The parameters to retrieve the adjacency list * @returns The adjacency list in a {@link Map} form. */ -export function getAdjacencyList( +export function getAdjacencyList< + Arc extends AdjacencyListArc, + Node extends AdjacencyListNode +>( params: AdjacencyListTransformationParams ): AdjacencyList { const { arcs, nodes } = params; @@ -117,10 +121,14 @@ export function getAdjacencyList( - entries.flatMap(([{ inputs }, item]) => inputs.map(({ _id }) => [_id, item])) + entries.flatMap(([{ inputs }, item]) => + inputs.map(({ _id }) => [_id, item]) + ) ); const outputsToNode = new Map( - entries.flatMap(([{ outputs }, item]) => outputs.map(({ _id }) => [_id, item])) + entries.flatMap(([{ outputs }, item]) => + outputs.map(({ _id }) => [_id, item]) + ) ); for (const arc of arcs) { diff --git a/libs/common/src/app/node/dtos/behaviors/node-behavior.base.dto.ts b/libs/common/src/app/node/dtos/behaviors/node-behavior.base.dto.ts index f879731b..fe3f2802 100644 --- a/libs/common/src/app/node/dtos/behaviors/node-behavior.base.dto.ts +++ b/libs/common/src/app/node/dtos/behaviors/node-behavior.base.dto.ts @@ -7,7 +7,9 @@ import { omit } from "../../../../utils/object-fns"; /** * Base behavior of any node */ -export abstract class NodeBehaviorBaseDto { +export abstract class NodeBehaviorBaseDto< + T extends NodeBehaviorType = NodeBehaviorType +> { /** * A unique identifier that determines the behavior. * @@ -21,7 +23,10 @@ export abstract class NodeBehaviorBaseDto; +export type NodeBehaviorDiscriminatorKey = keyof Pick< + NodeBehaviorBaseDto, + "type" +>; /** * The discriminator key for the node kind */ diff --git a/libs/common/src/app/node/dtos/behaviors/node-behavior.dto.ts b/libs/common/src/app/node/dtos/behaviors/node-behavior.dto.ts index 0c9227fe..b9467177 100644 --- a/libs/common/src/app/node/dtos/behaviors/node-behavior.dto.ts +++ b/libs/common/src/app/node/dtos/behaviors/node-behavior.dto.ts @@ -15,7 +15,10 @@ import { NodeBehaviorParameterOutputDto } from "./node-behavior.parameter-output.dto"; import { NodeBehaviorReferenceDto } from "./node-behavior.reference.dto"; -import { NodeBehaviorTriggerDto, NodeBehaviorTriggerUpdateDto } from "./node-behavior.trigger.dto"; +import { + NodeBehaviorTriggerDto, + NodeBehaviorTriggerUpdateDto +} from "./node-behavior.trigger.dto"; import { NodeBehaviorType } from "./node-behavior.type"; import { NodeBehaviorVariableDto, @@ -29,34 +32,54 @@ import { DiscriminatedType } from "../../../../types"; export const NODE_BEHAVIOR_DTOS = [ { name: NodeBehaviorType.CODE, value: NodeBehaviorCodeDto }, { name: NodeBehaviorType.FUNCTION, value: NodeBehaviorFunctionDto }, - { name: NodeBehaviorType.PARAMETER_IN, value: NodeBehaviorParameterInputDto }, - { name: NodeBehaviorType.PARAMETER_OUT, value: NodeBehaviorParameterOutputDto }, + { + name: NodeBehaviorType.PARAMETER_IN, + value: NodeBehaviorParameterInputDto + }, + { + name: NodeBehaviorType.PARAMETER_OUT, + value: NodeBehaviorParameterOutputDto + }, { name: NodeBehaviorType.REFERENCE, value: NodeBehaviorReferenceDto }, { name: NodeBehaviorType.TRIGGER, value: NodeBehaviorTriggerDto }, { name: NodeBehaviorType.VARIABLE, value: NodeBehaviorVariableDto } -] as const satisfies ReadonlyArray, NodeBehaviorType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeBehaviorType> +>; export const NODE_BEHAVIOR_CREATE_DTOS = [ { name: NodeBehaviorType.CODE, value: NodeBehaviorCodeDto }, { name: NodeBehaviorType.FUNCTION, value: NodeBehaviorFunctionCreateDto }, - { name: NodeBehaviorType.PARAMETER_IN, value: NodeBehaviorParameterInputCreateDto }, - { name: NodeBehaviorType.PARAMETER_OUT, value: NodeBehaviorParameterOutputCreateDto }, + { + name: NodeBehaviorType.PARAMETER_IN, + value: NodeBehaviorParameterInputCreateDto + }, + { + name: NodeBehaviorType.PARAMETER_OUT, + value: NodeBehaviorParameterOutputCreateDto + }, { name: NodeBehaviorType.REFERENCE, value: NodeBehaviorReferenceDto }, { name: NodeBehaviorType.TRIGGER, value: NodeBehaviorTriggerDto }, { name: NodeBehaviorType.VARIABLE, value: NodeBehaviorVariableDto } -] as const satisfies ReadonlyArray, NodeBehaviorType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeBehaviorType> +>; export const NODE_BEHAVIOR_UPDATE_DTOS = [ // input/output parameters, references can not be changed { name: NodeBehaviorType.TRIGGER, value: NodeBehaviorTriggerUpdateDto }, { name: NodeBehaviorType.VARIABLE, value: NodeBehaviorVariableUpdateDto } // TODO: more (Code, Function) -] as const satisfies ReadonlyArray, NodeBehaviorType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeBehaviorType> +>; /** * The union type of all node behaviors */ -export type NodeBehaviorDto = InstanceType<(typeof NODE_BEHAVIOR_DTOS)[number]["value"]>; +export type NodeBehaviorDto = InstanceType< + (typeof NODE_BEHAVIOR_DTOS)[number]["value"] +>; /** * The union type of all "create-behavior"s diff --git a/libs/common/src/app/node/dtos/behaviors/node-behavior.function.dto.ts b/libs/common/src/app/node/dtos/behaviors/node-behavior.function.dto.ts index f0254561..6f5e3b03 100644 --- a/libs/common/src/app/node/dtos/behaviors/node-behavior.function.dto.ts +++ b/libs/common/src/app/node/dtos/behaviors/node-behavior.function.dto.ts @@ -19,4 +19,7 @@ export class NodeBehaviorFunctionDto extends NodeBehaviorBaseDto { +export abstract class NodeTriggerBaseDto< + T extends NodeTriggerType = NodeTriggerType +> { /** * A unique identifier that determines the trigger. * @@ -20,4 +22,7 @@ export abstract class NodeTriggerBaseDto; +export type NodeTriggerDiscriminatorKey = keyof Pick< + NodeTriggerBaseDto, + "type" +>; diff --git a/libs/common/src/app/node/dtos/behaviors/triggers/node.trigger.dto.ts b/libs/common/src/app/node/dtos/behaviors/triggers/node.trigger.dto.ts index e951d403..bc698a8b 100644 --- a/libs/common/src/app/node/dtos/behaviors/triggers/node.trigger.dto.ts +++ b/libs/common/src/app/node/dtos/behaviors/triggers/node.trigger.dto.ts @@ -10,9 +10,13 @@ import { DiscriminatedType } from "../../../../../types"; */ export const NODE_TRIGGER_DTOS = [ { name: NodeTriggerType.CRON, value: NodeTriggerCronDto } -] as const satisfies ReadonlyArray, NodeTriggerType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeTriggerType> +>; /** * The union type of all node triggers */ -export type NodeTriggerDto = InstanceType<(typeof NODE_TRIGGER_DTOS)[number]["value"]>; +export type NodeTriggerDto = InstanceType< + (typeof NODE_TRIGGER_DTOS)[number]["value"] +>; diff --git a/libs/common/src/app/node/dtos/input/node-input.create.dto.ts b/libs/common/src/app/node/dtos/input/node-input.create.dto.ts index 06d8f2bf..129418ef 100644 --- a/libs/common/src/app/node/dtos/input/node-input.create.dto.ts +++ b/libs/common/src/app/node/dtos/input/node-input.create.dto.ts @@ -7,4 +7,7 @@ import { NodeInputDto } from "./node-input.dto"; * * Only possible for `code` behaviors. */ -export class NodeInputCreateDto extends PickType(NodeInputDto, ["name", "type"]) {} +export class NodeInputCreateDto extends PickType(NodeInputDto, [ + "name", + "type" +]) {} diff --git a/libs/common/src/app/node/dtos/kind/node-kind.base.dto.ts b/libs/common/src/app/node/dtos/kind/node-kind.base.dto.ts index 965cc970..7581d426 100644 --- a/libs/common/src/app/node/dtos/kind/node-kind.base.dto.ts +++ b/libs/common/src/app/node/dtos/kind/node-kind.base.dto.ts @@ -19,4 +19,5 @@ export type NodeKindDiscriminatorKey = keyof Pick; /** * The discriminator key for the node kind */ -export const NODE_KIND_DISCRIMINATOR_KEY = "type" as const satisfies NodeKindDiscriminatorKey; +export const NODE_KIND_DISCRIMINATOR_KEY = + "type" as const satisfies NodeKindDiscriminatorKey; diff --git a/libs/common/src/app/node/dtos/kind/node-kind.dto.ts b/libs/common/src/app/node/dtos/kind/node-kind.dto.ts index 28ea672d..0a6b44e3 100644 --- a/libs/common/src/app/node/dtos/kind/node-kind.dto.ts +++ b/libs/common/src/app/node/dtos/kind/node-kind.dto.ts @@ -14,7 +14,9 @@ import { DiscriminatedType } from "../../../../types"; export const NODE_KIND_DTOS = [ { name: NodeKindType.VERTEX, value: NodeKindVertexDto }, { name: NodeKindType.TEMPLATE, value: NodeKindTemplateDto } -] as const satisfies ReadonlyArray, NodeKindType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeKindType> +>; /** * All the possible node kinds for update @@ -22,13 +24,19 @@ export const NODE_KIND_DTOS = [ export const NODE_KIND_UPDATE_DTOS = [ { name: NodeKindType.VERTEX, value: NodeKindVertexUpdateDto }, { name: NodeKindType.TEMPLATE, value: NodeKindTemplateUpdateDto } -] as const satisfies ReadonlyArray, NodeKindType>>; +] as const satisfies ReadonlyArray< + DiscriminatedType, NodeKindType> +>; /** * The union type of all kinds */ -export type NodeKindDto = InstanceType<(typeof NODE_KIND_DTOS)[number]["value"]>; +export type NodeKindDto = InstanceType< + (typeof NODE_KIND_DTOS)[number]["value"] +>; /** * The union type of all "update-kind"s */ -export type NodeKindUpdateDto = InstanceType<(typeof NODE_KIND_UPDATE_DTOS)[number]["value"]>; +export type NodeKindUpdateDto = InstanceType< + (typeof NODE_KIND_UPDATE_DTOS)[number]["value"] +>; diff --git a/libs/common/src/app/node/dtos/kind/node-kind.template.update.dto.ts b/libs/common/src/app/node/dtos/kind/node-kind.template.update.dto.ts index 863b6330..d8d34baa 100644 --- a/libs/common/src/app/node/dtos/kind/node-kind.template.update.dto.ts +++ b/libs/common/src/app/node/dtos/kind/node-kind.template.update.dto.ts @@ -1,4 +1,9 @@ -import { IntersectionType, OmitType, PartialType, PickType } from "@nestjs/mapped-types"; +import { + IntersectionType, + OmitType, + PartialType, + PickType +} from "@nestjs/mapped-types"; import { NODE_KIND_DISCRIMINATOR_KEY } from "./node-kind.base.dto"; import { NodeKindTemplateDto } from "./node-kind.template.dto"; diff --git a/libs/common/src/app/node/dtos/kind/node-kind.vertex.update.dto.ts b/libs/common/src/app/node/dtos/kind/node-kind.vertex.update.dto.ts index 8d8d798c..3b718838 100644 --- a/libs/common/src/app/node/dtos/kind/node-kind.vertex.update.dto.ts +++ b/libs/common/src/app/node/dtos/kind/node-kind.vertex.update.dto.ts @@ -1,4 +1,9 @@ -import { IntersectionType, OmitType, PartialType, PickType } from "@nestjs/mapped-types"; +import { + IntersectionType, + OmitType, + PartialType, + PickType +} from "@nestjs/mapped-types"; import { NODE_KIND_DISCRIMINATOR_KEY } from "./node-kind.base.dto"; import { NodeKindVertexDto } from "./node-kind.vertex.dto"; @@ -8,5 +13,7 @@ import { NodeKindVertexDto } from "./node-kind.vertex.dto"; */ export class NodeKindVertexUpdateDto extends IntersectionType( PickType(NodeKindVertexDto, [NODE_KIND_DISCRIMINATOR_KEY]), - PartialType(OmitType(NodeKindVertexDto, [NODE_KIND_DISCRIMINATOR_KEY, "__graph"])) + PartialType( + OmitType(NodeKindVertexDto, [NODE_KIND_DISCRIMINATOR_KEY, "__graph"]) + ) ) {} diff --git a/libs/common/src/app/node/dtos/node.create.dto.spec.ts b/libs/common/src/app/node/dtos/node.create.dto.spec.ts index 1155a5ae..054057ef 100644 --- a/libs/common/src/app/node/dtos/node.create.dto.spec.ts +++ b/libs/common/src/app/node/dtos/node.create.dto.spec.ts @@ -29,7 +29,9 @@ describe("NodeCreateDto", () => { transformOptions ) as typeof toTransform; - expect(transformed.behavior).toBeInstanceOf(NodeBehaviorVariableDto); + expect(transformed.behavior).toBeInstanceOf( + NodeBehaviorVariableDto + ); expect(transformed.behavior.type).toBe(toTransform.behavior.type); expect(transformed.behavior.value).toBe(toTransform.behavior.value); @@ -52,11 +54,17 @@ describe("NodeCreateDto", () => { ) as typeof toTransform; expect(transformed.behavior).toBeInstanceOf(NodeBehaviorTriggerDto); - expect(transformed.behavior.trigger).toBeInstanceOf(NodeTriggerCronDto); + expect(transformed.behavior.trigger).toBeInstanceOf( + NodeTriggerCronDto + ); expect(transformed.behavior.type).toBe(toTransform.behavior.type); - expect(transformed.behavior.trigger.type).toBe(toTransform.behavior.trigger.type); - expect(transformed.behavior.trigger.cron).toBe(toTransform.behavior.trigger.cron); + expect(transformed.behavior.trigger.type).toBe( + toTransform.behavior.trigger.type + ); + expect(transformed.behavior.trigger.cron).toBe( + toTransform.behavior.trigger.cron + ); }); it("should fail when creating PARAMETER nodes", async () => { @@ -69,8 +77,14 @@ describe("NodeCreateDto", () => { name: "node" }; - const transformedIn = plainToInstance(NodeCreateDto, nodeIn, transformOptions); - expect(transformedIn.behavior).not.toBeInstanceOf(NodeBehaviorParameterInputDto); + const transformedIn = plainToInstance( + NodeCreateDto, + nodeIn, + transformOptions + ); + expect(transformedIn.behavior).not.toBeInstanceOf( + NodeBehaviorParameterInputDto + ); expect(await validate(transformedIn)).toHaveLength(1); const nodeOut: NodeCreateDto = { @@ -82,8 +96,14 @@ describe("NodeCreateDto", () => { name: "node" }; - const transformedOut = plainToInstance(NodeCreateDto, nodeOut, transformOptions); - expect(transformedOut.behavior).not.toBeInstanceOf(NodeBehaviorParameterOutputDto); + const transformedOut = plainToInstance( + NodeCreateDto, + nodeOut, + transformOptions + ); + expect(transformedOut.behavior).not.toBeInstanceOf( + NodeBehaviorParameterOutputDto + ); expect(await validate(transformedOut)).toHaveLength(1); }); }); @@ -92,7 +112,11 @@ describe("NodeCreateDto", () => { it("should transform `kind=VERTEX` correctly", () => { const toTransform = { behavior: { type: NodeBehaviorType.VARIABLE, value: 0 }, - kind: { __graph: 10, position: { x: 11, y: 12 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 10, + position: { x: 11, y: 12 }, + type: NodeKindType.VERTEX + }, name: "a node" } as const satisfies NodeCreateDto; @@ -106,14 +130,22 @@ describe("NodeCreateDto", () => { expect(transformed.kind.type).toBe(toTransform.kind.type); expect(transformed.kind.__graph).toBe(toTransform.kind.__graph); - expect(transformed.kind.position.x).toBe(toTransform.kind.position.x); - expect(transformed.kind.position.y).toBe(toTransform.kind.position.y); + expect(transformed.kind.position.x).toBe( + toTransform.kind.position.x + ); + expect(transformed.kind.position.y).toBe( + toTransform.kind.position.y + ); }); it("should validate `kind=VERTEX` correctly", async () => { const toTransform = { behavior: { type: NodeBehaviorType.VARIABLE, value: 0 }, - kind: { __graph: 10, position: { x: 11, y: 12 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 10, + position: { x: 11, y: 12 }, + type: NodeKindType.VERTEX + }, name: "a node" } as const satisfies NodeCreateDto; diff --git a/libs/common/src/app/node/dtos/node.create.dto.ts b/libs/common/src/app/node/dtos/node.create.dto.ts index e286f604..0833c69d 100644 --- a/libs/common/src/app/node/dtos/node.create.dto.ts +++ b/libs/common/src/app/node/dtos/node.create.dto.ts @@ -14,9 +14,10 @@ import { ENTITY_BASE_KEYS } from "../../../dtos/entity"; /** * The mandatory keys to create a [node]{@link NodeDto}. */ -export const NODE_CREATE_KEYS_MANDATORY = ["kind", "name"] as const satisfies ReadonlyArray< - keyof NodeDto ->; +export const NODE_CREATE_KEYS_MANDATORY = [ + "kind", + "name" +] as const satisfies ReadonlyArray; /** * DTO used to create [node]{@link NodeDto} diff --git a/libs/common/src/app/node/dtos/node.dto.ts b/libs/common/src/app/node/dtos/node.dto.ts index 1d642892..8d66b7c2 100644 --- a/libs/common/src/app/node/dtos/node.dto.ts +++ b/libs/common/src/app/node/dtos/node.dto.ts @@ -4,9 +4,15 @@ import { NODE_BEHAVIOR_DISCRIMINATOR_KEY, NodeBehaviorBaseDto } from "./behaviors/node-behavior.base.dto"; -import { NODE_BEHAVIOR_DTOS, NodeBehaviorDto } from "./behaviors/node-behavior.dto"; +import { + NODE_BEHAVIOR_DTOS, + NodeBehaviorDto +} from "./behaviors/node-behavior.dto"; import { NodeInputDto } from "./input/node-input.dto"; -import { NODE_KIND_DISCRIMINATOR_KEY, NodeKindBaseDto } from "./kind/node-kind.base.dto"; +import { + NODE_KIND_DISCRIMINATOR_KEY, + NodeKindBaseDto +} from "./kind/node-kind.base.dto"; import { NODE_KIND_DTOS, NodeKindDto } from "./kind/node-kind.dto"; import { NodeOutputDto } from "./output/node-output.dto"; import { DtoProperty } from "../../../dtos/dto"; diff --git a/libs/common/src/app/node/dtos/node.query.dto.spec.ts b/libs/common/src/app/node/dtos/node.query.dto.spec.ts index 643e519c..496025ac 100644 --- a/libs/common/src/app/node/dtos/node.query.dto.spec.ts +++ b/libs/common/src/app/node/dtos/node.query.dto.spec.ts @@ -13,17 +13,35 @@ describe("NodeQueryDto", () => { where: { behavior: { type: NodeBehaviorType.TRIGGER } } } as const satisfies NodeQueryDto; - const transformed1 = plainToInstance(NodeQueryDto, toTransform1, transformOptions); + const transformed1 = plainToInstance( + NodeQueryDto, + toTransform1, + transformOptions + ); expect( - (transformed1.where?.behavior?.type as EntityFilterValue | undefined)?.$eq + ( + transformed1.where?.behavior?.type as + | EntityFilterValue + | undefined + )?.$eq ).toBe(toTransform1.where.behavior.type); const toTransform2 = { - where: { kind: { active: { $ne: false }, type: NodeKindType.TEMPLATE } } + where: { + kind: { active: { $ne: false }, type: NodeKindType.TEMPLATE } + } } as const satisfies NodeQueryDto; - const transformed2 = plainToInstance(NodeQueryDto, toTransform2, transformOptions); + const transformed2 = plainToInstance( + NodeQueryDto, + toTransform2, + transformOptions + ); expect( - (transformed2.where?.kind?.type as EntityFilterValue | undefined)?.$eq + ( + transformed2.where?.kind?.type as + | EntityFilterValue + | undefined + )?.$eq ).toBe(toTransform2.where.kind.type); expect(validateSync(transformed2, validatorOptions)).toHaveLength(0); @@ -34,7 +52,13 @@ describe("NodeQueryDto", () => { order: [{ behavior: { type: "asc" } }] } as const satisfies NodeQueryDto; - const transformed1 = plainToInstance(NodeQueryDto, toTransform1, transformOptions); - expect(transformed1.order?.[0]?.behavior?.type).toBe(toTransform1.order[0].behavior.type); + const transformed1 = plainToInstance( + NodeQueryDto, + toTransform1, + transformOptions + ); + expect(transformed1.order?.[0]?.behavior?.type).toBe( + toTransform1.order[0].behavior.type + ); }); }); diff --git a/libs/common/src/app/node/dtos/node.update.dto.spec.ts b/libs/common/src/app/node/dtos/node.update.dto.spec.ts index f0fe0b2f..f314b111 100644 --- a/libs/common/src/app/node/dtos/node.update.dto.spec.ts +++ b/libs/common/src/app/node/dtos/node.update.dto.spec.ts @@ -1,6 +1,9 @@ import { plainToInstance } from "class-transformer"; -import { NodeBehaviorTriggerUpdateDto, NodeBehaviorVariableUpdateDto } from "./behaviors"; +import { + NodeBehaviorTriggerUpdateDto, + NodeBehaviorVariableUpdateDto +} from "./behaviors"; import { NodeBehaviorType } from "./behaviors/node-behavior.type"; import { NodeTriggerCronDto, NodeTriggerType } from "./behaviors/triggers"; import { NodeKindType } from "./kind/node-kind.type"; @@ -15,11 +18,19 @@ describe("NodeUpdateDto", () => { name: "a node" } as const satisfies NodeUpdateDto; - const transformed = plainToInstance(NodeUpdateDto, toTransform, transformOptions); + const transformed = plainToInstance( + NodeUpdateDto, + toTransform, + transformOptions + ); - expect(transformed.behavior).toBeInstanceOf(NodeBehaviorVariableUpdateDto); + expect(transformed.behavior).toBeInstanceOf( + NodeBehaviorVariableUpdateDto + ); expect(transformed.behavior!.type).toBe(toTransform.behavior.type); - expect((transformed.behavior! as NodeBehaviorVariableUpdateDto).value).toBeUndefined(); + expect( + (transformed.behavior! as NodeBehaviorVariableUpdateDto).value + ).toBeUndefined(); }); it("should transform `behavior=TRIGGER` correctly (with nested trigger content)", () => { @@ -37,12 +48,20 @@ describe("NodeUpdateDto", () => { transformOptions ) as typeof toTransform; - expect(transformed.behavior).toBeInstanceOf(NodeBehaviorTriggerUpdateDto); - expect(transformed.behavior.trigger).toBeInstanceOf(NodeTriggerCronDto); + expect(transformed.behavior).toBeInstanceOf( + NodeBehaviorTriggerUpdateDto + ); + expect(transformed.behavior.trigger).toBeInstanceOf( + NodeTriggerCronDto + ); expect(transformed.behavior.type).toBe(toTransform.behavior.type); - expect(transformed.behavior.trigger.type).toBe(toTransform.behavior.trigger.type); - expect(transformed.behavior.trigger.cron).toBe(toTransform.behavior.trigger.cron); + expect(transformed.behavior.trigger.type).toBe( + toTransform.behavior.trigger.type + ); + expect(transformed.behavior.trigger.cron).toBe( + toTransform.behavior.trigger.cron + ); }); }); @@ -60,8 +79,12 @@ describe("NodeUpdateDto", () => { ) as typeof toTransform; expect(transformed.kind.type).toBe(toTransform.kind.type); - expect(transformed.kind.position.x).toBe(toTransform.kind.position.x); - expect(transformed.kind.position.y).toBe(toTransform.kind.position.y); + expect(transformed.kind.position.x).toBe( + toTransform.kind.position.x + ); + expect(transformed.kind.position.y).toBe( + toTransform.kind.position.y + ); // ----------------- diff --git a/libs/common/src/app/node/dtos/node.update.dto.ts b/libs/common/src/app/node/dtos/node.update.dto.ts index 58b6ac5b..ddd2698c 100644 --- a/libs/common/src/app/node/dtos/node.update.dto.ts +++ b/libs/common/src/app/node/dtos/node.update.dto.ts @@ -22,7 +22,9 @@ import { NodeCreateDto } from "./node.create.dto"; * * Can not change the type of `kind` */ -export class NodeUpdateDto extends PartialType(OmitType(NodeCreateDto, ["behavior", "kind"])) { +export class NodeUpdateDto extends PartialType( + OmitType(NodeCreateDto, ["behavior", "kind"]) +) { @Expose() @IsOptional() @TypeTransformer(() => NodeBehaviorBaseDto, { diff --git a/libs/common/src/app/node/dtos/output/node-output.update.dto.ts b/libs/common/src/app/node/dtos/output/node-output.update.dto.ts index b207046a..c284a03e 100644 --- a/libs/common/src/app/node/dtos/output/node-output.update.dto.ts +++ b/libs/common/src/app/node/dtos/output/node-output.update.dto.ts @@ -7,4 +7,6 @@ import { NodeOutputDto } from "./node-output.dto"; * * Only to change the type of `variable`, `parameter-in`, `code` node-behaviors */ -export class NodeOutputUpdateDto extends PartialType(PickType(NodeOutputDto, ["name", "type"])) {} +export class NodeOutputUpdateDto extends PartialType( + PickType(NodeOutputDto, ["name", "type"]) +) {} diff --git a/libs/common/src/app/node/endpoints/node-input.endpoint.ts b/libs/common/src/app/node/endpoints/node-input.endpoint.ts index c1296c16..3df6bfa9 100644 --- a/libs/common/src/app/node/endpoints/node-input.endpoint.ts +++ b/libs/common/src/app/node/endpoints/node-input.endpoint.ts @@ -2,13 +2,20 @@ import { NodeJSON, NODES_ENDPOINT_PREFIX } from "./node.endpoint"; import { EntityId } from "../../../dtos/entity"; import { DtoToEntity } from "../../../dtos/entity/entity.types"; import { EntityEndpoint } from "../../../endpoints"; -import { NodeInputCreateDto, NodeInputDto, NodeInputUpdateDto } from "../dtos/input"; +import { + NodeInputCreateDto, + NodeInputDto, + NodeInputUpdateDto +} from "../dtos/input"; /** * Endpoint path parts for [nodes]{@link NodeInputDto} (without global prefix). * The parameter goes in the middle starting with a `/`. */ -export const NODE_INPUTS_ENDPOINT_PARTS = [NODES_ENDPOINT_PREFIX, "/inputs"] as const; +export const NODE_INPUTS_ENDPOINT_PARTS = [ + NODES_ENDPOINT_PREFIX, + "/inputs" +] as const; /** * Generates the endpoint for `node-input`s for a node. @@ -22,5 +29,9 @@ export function generateNodeInputsEndpoint(nodeId: EntityId) { } export type NodeInputJSON = NodeJSON["inputs"][number]; -export type NodeInputEndpoint | NodeInputJSON = NodeInputJSON> = - Pick, "create" | "delete" | "update">; +export type NodeInputEndpoint< + T extends DtoToEntity | NodeInputJSON = NodeInputJSON +> = Pick< + EntityEndpoint, + "create" | "delete" | "update" +>; diff --git a/libs/common/src/app/node/endpoints/node-output.endpoint.ts b/libs/common/src/app/node/endpoints/node-output.endpoint.ts index e60b115d..d697d69a 100644 --- a/libs/common/src/app/node/endpoints/node-output.endpoint.ts +++ b/libs/common/src/app/node/endpoints/node-output.endpoint.ts @@ -2,13 +2,20 @@ import { NodeJSON, NODES_ENDPOINT_PREFIX } from "./node.endpoint"; import { EntityId } from "../../../dtos/entity"; import { DtoToEntity } from "../../../dtos/entity/entity.types"; import { EntityEndpoint } from "../../../endpoints"; -import { NodeOutputCreateDto, NodeOutputDto, NodeOutputUpdateDto } from "../dtos/output"; +import { + NodeOutputCreateDto, + NodeOutputDto, + NodeOutputUpdateDto +} from "../dtos/output"; /** * Endpoint path parts for [nodes]{@link NodeOutputDto} (without global prefix). * The parameter goes in the middle starting with a `/`. */ -export const NODE_OUTPUTS_ENDPOINT_PARTS = [NODES_ENDPOINT_PREFIX, "/outputs"] as const; +export const NODE_OUTPUTS_ENDPOINT_PARTS = [ + NODES_ENDPOINT_PREFIX, + "/outputs" +] as const; /** * Generates the endpoint for `node-output`s for a node. diff --git a/libs/common/src/app/node/endpoints/node.endpoint.ts b/libs/common/src/app/node/endpoints/node.endpoint.ts index 84d9613a..79576cc3 100644 --- a/libs/common/src/app/node/endpoints/node.endpoint.ts +++ b/libs/common/src/app/node/endpoints/node.endpoint.ts @@ -10,8 +10,5 @@ import { NodeCreateDto, NodeDto, NodeUpdateDto } from "../dtos"; export const NODES_ENDPOINT_PREFIX = "/v1/nodes"; export type NodeJSON = Jsonify; -export type NodeEndpoint | NodeJSON = NodeJSON> = EntityEndpoint< - T, - NodeCreateDto, - NodeUpdateDto ->; +export type NodeEndpoint | NodeJSON = NodeJSON> = + EntityEndpoint; diff --git a/libs/common/src/app/node/io/input/are-inputs-readonly.spec.ts b/libs/common/src/app/node/io/input/are-inputs-readonly.spec.ts index 89bc8c80..419717b0 100644 --- a/libs/common/src/app/node/io/input/are-inputs-readonly.spec.ts +++ b/libs/common/src/app/node/io/input/are-inputs-readonly.spec.ts @@ -16,13 +16,15 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.REFERENCE, NodeBehaviorType.VARIABLE ] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnCreate(behavior)).toBeTrue(); + expect(areNodeInputsReadonlyOnCreate(behavior)).toBe(true); } }); it("should not be readonly", () => { - for (const behavior of [NodeBehaviorType.CODE] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnCreate(behavior)).toBeFalse(); + for (const behavior of [ + NodeBehaviorType.CODE + ] satisfies NodeBehaviorType[]) { + expect(areNodeInputsReadonlyOnCreate(behavior)).toBe(false); } }); }); @@ -36,7 +38,7 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.REFERENCE, NodeBehaviorType.VARIABLE ] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnUpdate(behavior)).toBeTrue(); + expect(areNodeInputsReadonlyOnUpdate(behavior)).toBe(true); } }); @@ -45,7 +47,7 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.CODE, NodeBehaviorType.PARAMETER_OUT ] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnUpdate(behavior)).toBeFalse(); + expect(areNodeInputsReadonlyOnUpdate(behavior)).toBe(false); } }); }); @@ -60,13 +62,15 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.REFERENCE, NodeBehaviorType.VARIABLE ] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnDelete(behavior)).toBeTrue(); + expect(areNodeInputsReadonlyOnDelete(behavior)).toBe(true); } }); it("should not be readonly", () => { - for (const behavior of [NodeBehaviorType.CODE] satisfies NodeBehaviorType[]) { - expect(areNodeInputsReadonlyOnDelete(behavior)).toBeFalse(); + for (const behavior of [ + NodeBehaviorType.CODE + ] satisfies NodeBehaviorType[]) { + expect(areNodeInputsReadonlyOnDelete(behavior)).toBe(false); } }); }); diff --git a/libs/common/src/app/node/io/input/are-inputs-readonly.ts b/libs/common/src/app/node/io/input/are-inputs-readonly.ts index 86bb0f33..00ffb7be 100644 --- a/libs/common/src/app/node/io/input/are-inputs-readonly.ts +++ b/libs/common/src/app/node/io/input/are-inputs-readonly.ts @@ -6,7 +6,9 @@ import { NodeBehaviorType } from "../../dtos/behaviors/node-behavior.type"; * @param behavior of the node to test * @returns if the inputs of the node with the given behavior are readonly */ -export function areNodeInputsReadonlyOnCreate(behavior: NodeBehaviorType): boolean { +export function areNodeInputsReadonlyOnCreate( + behavior: NodeBehaviorType +): boolean { switch (behavior) { case NodeBehaviorType.CODE: return false; @@ -26,7 +28,9 @@ export function areNodeInputsReadonlyOnCreate(behavior: NodeBehaviorType): boole * @param behavior of the node to test * @returns if the inputs of the node with the given behavior are readonly */ -export function areNodeInputsReadonlyOnUpdate(behavior: NodeBehaviorType): boolean { +export function areNodeInputsReadonlyOnUpdate( + behavior: NodeBehaviorType +): boolean { switch (behavior) { case NodeBehaviorType.CODE: case NodeBehaviorType.PARAMETER_OUT: @@ -46,7 +50,9 @@ export function areNodeInputsReadonlyOnUpdate(behavior: NodeBehaviorType): boole * @param behavior of the node to test * @returns if the inputs of the node with the given behavior are readonly */ -export function areNodeInputsReadonlyOnDelete(behavior: NodeBehaviorType): boolean { +export function areNodeInputsReadonlyOnDelete( + behavior: NodeBehaviorType +): boolean { switch (behavior) { case NodeBehaviorType.CODE: return false; diff --git a/libs/common/src/app/node/io/node-io.functions.spec.ts b/libs/common/src/app/node/io/node-io.functions.spec.ts index 72b000a5..4c53b06d 100644 --- a/libs/common/src/app/node/io/node-io.functions.spec.ts +++ b/libs/common/src/app/node/io/node-io.functions.spec.ts @@ -1,4 +1,7 @@ -import { castNodeIoValueTo, CastNodeIoValueToException } from "./node-io.functions"; +import { + castNodeIoValueTo, + CastNodeIoValueToException +} from "./node-io.functions"; import { NODE_IO_VOID, NodeIoType, NodeIoValueFromType } from "./node-io.type"; describe("castNodeIoValueTo", () => { @@ -32,7 +35,9 @@ describe("castNodeIoValueTo", () => { { a: {}, b: null } ] ] satisfies Array<[unknown, NodeIoValueFromType]>) { - expect(castNodeIoValueTo(NodeIoType.JSON, value)).toStrictEqual(expected); + expect(castNodeIoValueTo(NodeIoType.JSON, value)).toStrictEqual( + expected + ); } }); @@ -45,7 +50,9 @@ describe("castNodeIoValueTo", () => { false, { a: 1, b: true } ] satisfies Array>) { - expect(castNodeIoValueTo(NodeIoType.ANY, value)).toStrictEqual(value); + expect(castNodeIoValueTo(NodeIoType.ANY, value)).toStrictEqual( + value + ); } }); @@ -58,7 +65,9 @@ describe("castNodeIoValueTo", () => { false, { a: 1, b: true } ] satisfies unknown[]) { - expect(castNodeIoValueTo(NodeIoType.VOID, value)).toStrictEqual(NODE_IO_VOID); + expect(castNodeIoValueTo(NodeIoType.VOID, value)).toStrictEqual( + NODE_IO_VOID + ); } }); diff --git a/libs/common/src/app/node/io/node-io.functions.ts b/libs/common/src/app/node/io/node-io.functions.ts index 8840df3a..a3e32fe9 100644 --- a/libs/common/src/app/node/io/node-io.functions.ts +++ b/libs/common/src/app/node/io/node-io.functions.ts @@ -17,7 +17,7 @@ export class CastNodeIoValueToException extends Error { cause?: unknown ) { let sValue = String(value); - if (sValue.length > 10) { + if (10 < sValue.length) { sValue = `${sValue.slice(0, 10)}...`; } @@ -56,7 +56,8 @@ export function castNodeIoValueTo( } case NodeIoType.NUMBER: { - const casted: NodeIoValueFromType = Number(value); + const casted: NodeIoValueFromType = + Number(value); if (Number.isNaN(casted)) { break; } diff --git a/libs/common/src/app/node/io/output/are-outputs-readonly.spec.ts b/libs/common/src/app/node/io/output/are-outputs-readonly.spec.ts index c518222f..148af7c2 100644 --- a/libs/common/src/app/node/io/output/are-outputs-readonly.spec.ts +++ b/libs/common/src/app/node/io/output/are-outputs-readonly.spec.ts @@ -10,7 +10,7 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.TRIGGER, NodeBehaviorType.REFERENCE ] satisfies NodeBehaviorType[]) { - expect(areNodeOutputsReadonlyOnUpdate(behavior)).toBeTrue(); + expect(areNodeOutputsReadonlyOnUpdate(behavior)).toBe(true); } }); @@ -20,7 +20,7 @@ describe("areNodeOutputsReadonly", () => { NodeBehaviorType.PARAMETER_IN, NodeBehaviorType.VARIABLE ] satisfies NodeBehaviorType[]) { - expect(areNodeOutputsReadonlyOnUpdate(behavior)).toBeFalse(); + expect(areNodeOutputsReadonlyOnUpdate(behavior)).toBe(false); } }); }); diff --git a/libs/common/src/app/node/io/output/are-outputs-readonly.ts b/libs/common/src/app/node/io/output/are-outputs-readonly.ts index 7812fa9d..54e4b772 100644 --- a/libs/common/src/app/node/io/output/are-outputs-readonly.ts +++ b/libs/common/src/app/node/io/output/are-outputs-readonly.ts @@ -6,7 +6,9 @@ import { NodeBehaviorType } from "../../dtos/behaviors/node-behavior.type"; * @param behavior of the node to test * @returns if the outputs of the node with the given behavior are readonly */ -export function areNodeOutputsReadonlyOnUpdate(behavior: NodeBehaviorType): boolean { +export function areNodeOutputsReadonlyOnUpdate( + behavior: NodeBehaviorType +): boolean { switch (behavior) { case NodeBehaviorType.CODE: case NodeBehaviorType.PARAMETER_IN: diff --git a/libs/common/src/app/user/dtos/user.create.dto.ts b/libs/common/src/app/user/dtos/user.create.dto.ts index ec734630..056c8346 100644 --- a/libs/common/src/app/user/dtos/user.create.dto.ts +++ b/libs/common/src/app/user/dtos/user.create.dto.ts @@ -1,4 +1,9 @@ -import { IntersectionType, OmitType, PartialType, PickType } from "@nestjs/mapped-types"; +import { + IntersectionType, + OmitType, + PartialType, + PickType +} from "@nestjs/mapped-types"; import { UserDto } from "./user.dto"; import { ENTITY_BASE_KEYS } from "../../../dtos/entity"; @@ -6,7 +11,9 @@ import { ENTITY_BASE_KEYS } from "../../../dtos/entity"; /** * The mandatory keys to create an [user]{@link UserDto}. */ -export const USER_CREATE_KEYS_MANDATORY = ["email"] as const satisfies ReadonlyArray; +export const USER_CREATE_KEYS_MANDATORY = [ + "email" +] as const satisfies ReadonlyArray; /** * DTO used to create [users]{@link UserDto} @@ -14,5 +21,7 @@ export const USER_CREATE_KEYS_MANDATORY = ["email"] as const satisfies ReadonlyA */ export class UserCreateDto extends IntersectionType( PickType(UserDto, USER_CREATE_KEYS_MANDATORY), - PartialType(OmitType(UserDto, [...ENTITY_BASE_KEYS, ...USER_CREATE_KEYS_MANDATORY])) + PartialType( + OmitType(UserDto, [...ENTITY_BASE_KEYS, ...USER_CREATE_KEYS_MANDATORY]) + ) ) {} diff --git a/libs/common/src/app/user/dtos/user.update.dto.ts b/libs/common/src/app/user/dtos/user.update.dto.ts index 16c07bd1..cacaf288 100644 --- a/libs/common/src/app/user/dtos/user.update.dto.ts +++ b/libs/common/src/app/user/dtos/user.update.dto.ts @@ -6,4 +6,6 @@ import { UserCreateDto } from "./user.create.dto"; * DTO used to update [users]{@link UserDto} * in its {@link UserEndpoint endpoint}. */ -export class UserUpdateDto extends PartialType(OmitType(UserCreateDto, ["email"])) {} +export class UserUpdateDto extends PartialType( + OmitType(UserCreateDto, ["email"]) +) {} diff --git a/libs/common/src/app/user/endpoints/user.endpoint.ts b/libs/common/src/app/user/endpoints/user.endpoint.ts index a6f3f510..7269115d 100644 --- a/libs/common/src/app/user/endpoints/user.endpoint.ts +++ b/libs/common/src/app/user/endpoints/user.endpoint.ts @@ -10,8 +10,5 @@ import { UserCreateDto, UserDto, UserUpdateDto } from "../dtos"; export const USERS_ENDPOINT_PREFIX = "/v1/users"; export type UserJSON = Jsonify; -export type UserEndpoint | UserJSON = UserJSON> = EntityEndpoint< - T, - UserCreateDto, - UserUpdateDto ->; +export type UserEndpoint | UserJSON = UserJSON> = + EntityEndpoint; diff --git a/libs/common/src/app/workflow/dtos/workflow.create.dto.ts b/libs/common/src/app/workflow/dtos/workflow.create.dto.ts index 7cd8c815..ce26b548 100644 --- a/libs/common/src/app/workflow/dtos/workflow.create.dto.ts +++ b/libs/common/src/app/workflow/dtos/workflow.create.dto.ts @@ -6,16 +6,17 @@ import { ENTITY_BASE_KEYS } from "../../../dtos/entity"; /** * The mandatory keys to create a [workflow]{@link WorkflowDto}. */ -export const WORKFLOW_CREATE_KEYS_MANDATORY = ["name"] as const satisfies ReadonlyArray< - keyof WorkflowDto ->; +export const WORKFLOW_CREATE_KEYS_MANDATORY = [ + "name" +] as const satisfies ReadonlyArray; /** * The keys that can not be set */ -export const WORKFLOW_KEYS_READONLY = ["__graph", "graph"] as const satisfies ReadonlyArray< - keyof WorkflowDto ->; +export const WORKFLOW_KEYS_READONLY = [ + "__graph", + "graph" +] as const satisfies ReadonlyArray; /** * DTO used to create [workflow]{@link WorkflowDto} diff --git a/libs/common/src/app/workflow/dtos/workflow.dto.ts b/libs/common/src/app/workflow/dtos/workflow.dto.ts index 7caa5135..607ba868 100644 --- a/libs/common/src/app/workflow/dtos/workflow.dto.ts +++ b/libs/common/src/app/workflow/dtos/workflow.dto.ts @@ -1,4 +1,10 @@ -import { IsBoolean, IsNumber, IsOptional, IsString, MinLength } from "class-validator"; +import { + IsBoolean, + IsNumber, + IsOptional, + IsString, + MinLength +} from "class-validator"; import { DtoProperty } from "../../../dtos/dto"; import { EntityDto } from "../../../dtos/entity"; diff --git a/libs/common/src/app/workflow/endpoints/workflow.endpoint.ts b/libs/common/src/app/workflow/endpoints/workflow.endpoint.ts index c83d4d19..d53fd551 100644 --- a/libs/common/src/app/workflow/endpoints/workflow.endpoint.ts +++ b/libs/common/src/app/workflow/endpoints/workflow.endpoint.ts @@ -12,8 +12,9 @@ import { WorkflowCreateDto, WorkflowDto, WorkflowUpdateDto } from "../dtos"; export const WORKFLOWS_ENDPOINT_PREFIX = "/v1/workflows"; export type WorkflowJSON = Jsonify; -export interface WorkflowEndpoint | WorkflowJSON = WorkflowJSON> - extends EntityEndpoint { +export interface WorkflowEndpoint< + T extends DtoToEntity | WorkflowJSON = WorkflowJSON +> extends EntityEndpoint { /** * Loads the graph of the given workflow. * This is equivalent to loading directly the graph. @@ -23,7 +24,9 @@ export interface WorkflowEndpoint | WorkflowJ */ lookForGraph( id: EntityId - ): Promise : DtoToEntity>; + ): Promise< + T extends WorkflowJSON ? Jsonify : DtoToEntity + >; } /** diff --git a/libs/common/src/dtos/dto/dto.decorator.ts b/libs/common/src/dtos/dto/dto.decorator.ts index 470e4143..73d08ba9 100644 --- a/libs/common/src/dtos/dto/dto.decorator.ts +++ b/libs/common/src/dtos/dto/dto.decorator.ts @@ -20,7 +20,11 @@ export function DtoProperty>( return (target, propertyKey) => { const constructor = target.constructor as Type; dtoStorage.addPropertyKey(constructor, propertyKey); - dtoStorage.storePropertyOptions(target as Type, propertyKey, options as never); + dtoStorage.storePropertyOptions( + target as Type, + propertyKey, + options as never + ); if (type) { // TODO: an option to disable? diff --git a/libs/common/src/dtos/dto/dto.storage.ts b/libs/common/src/dtos/dto/dto.storage.ts index 0e691836..da2afc12 100644 --- a/libs/common/src/dtos/dto/dto.storage.ts +++ b/libs/common/src/dtos/dto/dto.storage.ts @@ -118,17 +118,23 @@ class DtoStorage { // TODO: a way to get from the class-transformer directly or use the DtoDecorator to set class-transformer const forwardType = options.type(); if (forwardType === null) { - throw new DtoError("Can do nothing with a `null` only type or unknown type."); + throw new DtoError( + "Can do nothing with a `null` only type or unknown type." + ); } return forwardType; } - const metadataType = Reflect.getMetadata("design:type", source, propertyKey) as - | Type - | undefined; + const metadataType = Reflect.getMetadata( + "design:type", + source, + propertyKey + ) as Type | undefined; if (metadataType === undefined) { - throw new DtoError("Can do nothing with a `null` only type or unknown type."); + throw new DtoError( + "Can do nothing with a `null` only type or unknown type." + ); } return metadataType as DtoType; @@ -139,10 +145,10 @@ class DtoStorage { * @param propertyKey The key of the property to search * @returns The Option, if found, */ - public getPropertyOptions = never>( - source: Type, - propertyKey: DtoPropertyKey - ) { + public getPropertyOptions< + T = never, + P extends Extract = never + >(source: Type, propertyKey: DtoPropertyKey) { return Reflect.getMetadata(this.META_INFO, source, propertyKey) as | DtoPropertyOptions | undefined; diff --git a/libs/common/src/dtos/dto/dto.types.ts b/libs/common/src/dtos/dto/dto.types.ts index 99a31631..9cf0b75f 100644 --- a/libs/common/src/dtos/dto/dto.types.ts +++ b/libs/common/src/dtos/dto/dto.types.ts @@ -11,7 +11,10 @@ export type DtoType = AbstractClass | Class; * Discriminator object containing the type information to select a proper type * during transformation when a discriminator property is provided. */ -export interface DtoDiscriminator> { +export interface DtoDiscriminator< + T extends object, + P extends Extract +> { /** * The name of the property which holds the type information in the received object. */ @@ -36,7 +39,10 @@ export interface DtoDiscriminator = never> { +export interface DtoPropertyOptions< + T = never, + P extends Extract = never +> { /** * Does currently nothing * diff --git a/libs/common/src/dtos/entity/entity.types.ts b/libs/common/src/dtos/entity/entity.types.ts index fe9666ed..a6182ed8 100644 --- a/libs/common/src/dtos/entity/entity.types.ts +++ b/libs/common/src/dtos/entity/entity.types.ts @@ -6,7 +6,9 @@ import { EntityDto } from "./entity.dto"; * Transforms a DTO to its Entity form */ export type DtoToEntity = { - [K in keyof T]: NonNullable extends ReadonlyArray + [K in keyof T]: NonNullable extends ReadonlyArray< + infer U extends EntityDto + > ? Collection> : NonNullable extends EntityDto ? DtoToEntity> | Extract diff --git a/libs/common/src/dtos/find-query.dto.spec.ts b/libs/common/src/dtos/find-query.dto.spec.ts index f86a8dfe..be0614a8 100644 --- a/libs/common/src/dtos/find-query.dto.spec.ts +++ b/libs/common/src/dtos/find-query.dto.spec.ts @@ -28,7 +28,8 @@ describe("FindQueryDto", () => { class FindQueryDto extends FindQueryDtoOf(NestedDto) {} - const transform = (object: object) => plainToInstance(FindQueryDto, object, transformOptions); + const transform = (object: object) => + plainToInstance(FindQueryDto, object, transformOptions); const validate = (object: object) => validateSync(object, validatorOptions); for (const key of ["limit", "skip"] satisfies Array) { @@ -60,7 +61,9 @@ describe("FindQueryDto", () => { ]; for (const order of orders) { - const errors = validate(transform({ order } satisfies FindQueryDto)); + const errors = validate( + transform({ order } satisfies FindQueryDto) + ); expect(errors).toHaveLength(0); } }); @@ -72,7 +75,9 @@ describe("FindQueryDto", () => { ]; for (const order of orders) { - const errors = validate(transform({ order } satisfies FindQueryDto)); + const errors = validate( + transform({ order } satisfies FindQueryDto) + ); expect(errors).not.toHaveLength(0); } }); @@ -86,7 +91,9 @@ describe("FindQueryDto", () => { ]; for (const where of wheres) { - const errors = validate(transform({ where } satisfies FindQueryDto)); + const errors = validate( + transform({ where } satisfies FindQueryDto) + ); expect(errors).toHaveLength(0); } }); @@ -98,7 +105,9 @@ describe("FindQueryDto", () => { ]; for (const where of wheres) { - const errors = validate(transform({ where } satisfies FindQueryDto)); + const errors = validate( + transform({ where } satisfies FindQueryDto) + ); expect(errors).not.toHaveLength(0); } }); diff --git a/libs/common/src/dtos/find-query.dto.ts b/libs/common/src/dtos/find-query.dto.ts index 4e4fb465..9ebbe791 100644 --- a/libs/common/src/dtos/find-query.dto.ts +++ b/libs/common/src/dtos/find-query.dto.ts @@ -1,6 +1,12 @@ import type { Type } from "@nestjs/common"; import { Expose, Type as TypeTransformer } from "class-transformer"; -import { IsArray, IsNumber, IsOptional, Min, ValidateNested } from "class-validator"; +import { + IsArray, + IsNumber, + IsOptional, + Min, + ValidateNested +} from "class-validator"; import { FindQueryOrderDtoOf } from "./find-query/find-query-order.dto"; import { FindQueryWhereDtoOf } from "./find-query/find-query-where.dto"; @@ -41,7 +47,9 @@ export abstract class FindQueryDto implements EntityFindQuery { * @param dto The class used to determine the transformation and validations * @returns The generated class */ -export function FindQueryDtoOf(dto: Type): Type> { +export function FindQueryDtoOf( + dto: Type +): Type> { const OrderDto = FindQueryOrderDtoOf(dto); const WhereDto = FindQueryWhereDtoOf(dto); diff --git a/libs/common/src/dtos/find-query/find-query-order.dto.spec.ts b/libs/common/src/dtos/find-query/find-query-order.dto.spec.ts index 2cf0d176..2cb5069a 100644 --- a/libs/common/src/dtos/find-query/find-query-order.dto.spec.ts +++ b/libs/common/src/dtos/find-query/find-query-order.dto.spec.ts @@ -7,7 +7,11 @@ import { DtoProperty } from "../dto"; describe("FindQueryOrderDto", () => { const validate = (value: object) => - validateSync(value, { ...validatorOptions, forbidNonWhitelisted: true, whitelist: true }); + validateSync(value, { + ...validatorOptions, + forbidNonWhitelisted: true, + whitelist: true + }); describe("Validation on a flat DTO", () => { class FlatDto { @@ -177,9 +181,9 @@ describe("FindQueryOrderDto", () => { expect(errors).toHaveLength(0); } - const toTransform = { nested: { a: "asc" } } as const satisfies InstanceType< - typeof NestedOrderDto - >; + const toTransform = { + nested: { a: "asc" } + } as const satisfies InstanceType; const transformed = transform(toTransform) as typeof toTransform; expect(transformed.nested.a).toBe(toTransform.nested.a); }); diff --git a/libs/common/src/dtos/find-query/find-query-order.dto.ts b/libs/common/src/dtos/find-query/find-query-order.dto.ts index 987f37f3..e56679f0 100644 --- a/libs/common/src/dtos/find-query/find-query-order.dto.ts +++ b/libs/common/src/dtos/find-query/find-query-order.dto.ts @@ -1,7 +1,12 @@ import { Singleton } from "@heap-code/singleton"; import type { Type } from "@nestjs/common"; import { IntersectionType } from "@nestjs/mapped-types"; -import { Expose, plainToInstance, Transform, Type as TypeTransformer } from "class-transformer"; +import { + Expose, + plainToInstance, + Transform, + Type as TypeTransformer +} from "class-transformer"; import { IsIn, IsOptional, ValidateNested } from "class-validator"; import { EntityOrder, OrderValues } from "../../endpoints"; @@ -14,12 +19,16 @@ function getPropertyDecorators( ): PropertyDecorator[] { // We suppose that Object is a union or a "bad" type definition // undefined for null - if ([Boolean, String, Date, Number, Object, undefined].includes(type as never)) { + if ( + [Boolean, String, Date, Number, Object, undefined].includes( + type as never + ) + ) { return [IsIn(OrderValues)]; } // Due to its laziness, calculated only once and when needed (and avoid circular calls) - // eslint-disable-next-line no-use-before-define -- Created below + const baseType = new Singleton(() => FindQueryOrderDtoOf(type as never)); const { discriminator } = options; @@ -31,8 +40,9 @@ function getPropertyDecorators( const discriminatedType = new Singleton(() => discriminator.subTypes.reduce( // TODO: remove `IntersectionType` and do it "manually" - // eslint-disable-next-line no-use-before-define -- Created below - (intersected, { value }) => IntersectionType(intersected, FindQueryOrderDtoOf(value)), + + (intersected, { value }) => + IntersectionType(intersected, FindQueryOrderDtoOf(value)), baseType.get() ) ); @@ -54,13 +64,21 @@ function getPropertyDecorators( * @param source The class used to determine the transformation and validations * @returns The generated class */ -export function FindQueryOrderDtoOf(source: DtoType): Type> { +export function FindQueryOrderDtoOf( + source: DtoType +): Type> { // eslint-disable-next-line @typescript-eslint/no-extraneous-class -- The class is constructed below class OrderDto {} for (const key of dtoStorage.getPropertyKeys(source)) { - const type = dtoStorage.getPropertyType(source.prototype as Type, key); - const options = dtoStorage.getPropertyOptions(source.prototype as never, key); + const type = dtoStorage.getPropertyType( + source.prototype as Type, + key + ); + const options = dtoStorage.getPropertyOptions( + source.prototype as never, + key + ); Reflect.decorate( [Expose(), IsOptional(), ...getPropertyDecorators(type, options)], @@ -69,6 +87,8 @@ export function FindQueryOrderDtoOf(source: DtoType): Type< ); } - Object.defineProperty(OrderDto, "name", { value: `${OrderDto.name}${source.name}` }); + Object.defineProperty(OrderDto, "name", { + value: `${OrderDto.name}${source.name}` + }); return OrderDto; } diff --git a/libs/common/src/dtos/find-query/find-query-where.dto.spec.ts b/libs/common/src/dtos/find-query/find-query-where.dto.spec.ts index a5103e24..35ab3d37 100644 --- a/libs/common/src/dtos/find-query/find-query-where.dto.spec.ts +++ b/libs/common/src/dtos/find-query/find-query-where.dto.spec.ts @@ -33,11 +33,20 @@ describe("FindQueryWhereDto", () => { class FlatWhereDto extends FindQueryWhereDtoOf(FlatDto) {} const transform = (object: object) => - plainToInstance(FlatWhereDto, object, omit(transformOptions, ["strategy"])); + plainToInstance( + FlatWhereDto, + object, + omit(transformOptions, ["strategy"]) + ); it("should be valid (without logical)", () => { const wheres: FlatWhereDto[] = [ - { a: { $gt: 3, $lt: 9 }, b: "ok", c: null, f: { $like: "abc" } }, + { + a: { $gt: 3, $lt: 9 }, + b: "ok", + c: null, + f: { $like: "abc" } + }, { a: 0, b: { $gt: "abc" }, d: { $lt: new Date() }, f: null }, {} ]; @@ -52,8 +61,18 @@ describe("FindQueryWhereDto", () => { const wheres: Array> = [ { $and: [ - { a: { $gt: 3, $lt: 9 }, b: "ok", c: null, f: { $like: "abc" } }, - { a: 0, b: { $gt: "abc" }, d: { $lt: new Date() }, f: null }, + { + a: { $gt: 3, $lt: 9 }, + b: "ok", + c: null, + f: { $like: "abc" } + }, + { + a: 0, + b: { $gt: "abc" }, + d: { $lt: new Date() }, + f: null + }, {} ] }, @@ -82,16 +101,20 @@ describe("FindQueryWhereDto", () => { expect(errors).toHaveLength(4); const errA = errors.find( - ({ property }) => property === ("a" satisfies keyof FlatWhereDto) + ({ property }) => + property === ("a" satisfies keyof FlatWhereDto) ); const errD = errors.find( - ({ property }) => property === ("d" satisfies keyof FlatWhereDto) + ({ property }) => + property === ("d" satisfies keyof FlatWhereDto) ); const errE = errors.find( - ({ property }) => property === ("e" satisfies keyof FlatWhereDto) + ({ property }) => + property === ("e" satisfies keyof FlatWhereDto) ); const errF = errors.find( - ({ property }) => property === ("f" satisfies keyof FlatWhereDto) + ({ property }) => + property === ("f" satisfies keyof FlatWhereDto) ); expect(errA).toBeDefined(); expect(errD).toBeDefined(); @@ -100,7 +123,9 @@ describe("FindQueryWhereDto", () => { expect(errA?.children?.[0].constraints).toHaveProperty(IS_NUMBER); expect(errD?.children?.[0].constraints).toHaveProperty(IS_DATE); - expect(errE?.children?.[0].constraints).toHaveProperty("whitelistValidation"); + expect(errE?.children?.[0].constraints).toHaveProperty( + "whitelistValidation" + ); expect(errF?.children?.[0].constraints).toHaveProperty(IS_STRING); }); }); @@ -177,7 +202,11 @@ describe("FindQueryWhereDto", () => { class NestedWhereDto extends FindQueryWhereDtoOf(Dto) {} const transform = (object: object) => - plainToInstance(NestedWhereDto, object, omit(transformOptions, ["strategy"])); + plainToInstance( + NestedWhereDto, + object, + omit(transformOptions, ["strategy"]) + ); it("should be valid", () => { const wheres: NestedWhereDto[] = [ diff --git a/libs/common/src/dtos/find-query/find-query-where.dto.ts b/libs/common/src/dtos/find-query/find-query-where.dto.ts index f820108d..3330325d 100644 --- a/libs/common/src/dtos/find-query/find-query-where.dto.ts +++ b/libs/common/src/dtos/find-query/find-query-where.dto.ts @@ -40,7 +40,10 @@ interface Options { // Gets the "base" DTO for a given type /** @internal */ -function getWhereDtoType(type: DtoType, options: DtoPropertyOptions = {}) { +function getWhereDtoType( + type: DtoType, + options: DtoPropertyOptions = {} +) { const { nullable } = options; switch (type) { @@ -54,7 +57,6 @@ function getWhereDtoType(type: DtoType, options: DtoPropertyOptions = {} return nullable ? WhereStringNullableDto : WhereStringDto; } - // eslint-disable-next-line no-use-before-define -- created below return generateWhereType(type, class NestedWhereDto {}); } @@ -74,7 +76,11 @@ function transformWhereDto( // For "Primitive" types switch ( !isObject(value) && - (sourceType as unknown as typeof Boolean | typeof Date | typeof Number | typeof String) + (sourceType as unknown as + | typeof Boolean + | typeof Date + | typeof Number + | typeof String) ) { // Get back to this function with an object (for transformation) case Boolean: @@ -87,14 +93,18 @@ function transformWhereDto( return transformWhereDto( typeSingleton, { - $eq: isDateString(value) ? new Date(value as string) : (value as never) + $eq: isDateString(value) + ? new Date(value as string) + : (value as never) } satisfies WhereDateDto, options ); case Number: return transformWhereDto( typeSingleton, - { $eq: value === null ? null : +value } satisfies WhereNumberNullableDto, + { + $eq: value === null ? null : +value + } satisfies WhereNumberNullableDto, options ); case String: @@ -119,17 +129,31 @@ function transformWhereDto( const propertyType = typeof discriminatedProperty; // Only narrow type if the discriminator is "usable" - if (propertyType === "boolean" || propertyType === "number" || propertyType === "string") { - const subType = subTypes.find(({ name }) => name === discriminatedProperty); + if ( + propertyType === "boolean" || + propertyType === "number" || + propertyType === "string" + ) { + const subType = subTypes.find( + ({ name }) => name === discriminatedProperty + ); if (subType) { - return plainToInstance(getWhereDtoType(subType.value), value, transformer); + return plainToInstance( + getWhereDtoType(subType.value), + value, + transformer + ); } return UNKNOWN_DISCRIMINATED_TYPE; } } - return plainToInstance(getWhereDtoType(sourceType, dto), value, transformer); + return plainToInstance( + getWhereDtoType(sourceType, dto), + value, + transformer + ); } /** @internal */ @@ -138,17 +162,24 @@ function generateWhereType(source: DtoType, target: Type) { const type = new Singleton(() => dtoStorage.getPropertyType(source.prototype as Type, key) ); - const dtoOptions = dtoStorage.getPropertyOptions(source.prototype as Type, key); + const dtoOptions = dtoStorage.getPropertyOptions( + source.prototype as Type, + key + ); Reflect.decorate( [ Expose(), IsOptional(), Transform(({ key, obj, options }) => - transformWhereDto(type, (obj as Record)[key], { - dto: dtoOptions, - transformer: options - }) + transformWhereDto( + type, + (obj as Record)[key], + { + dto: dtoOptions, + transformer: options + } + ) ), NotEquals(UNKNOWN_DISCRIMINATED_TYPE, { message: "The discriminated type was not determined" @@ -170,7 +201,9 @@ function generateWhereType(source: DtoType, target: Type) { * @param dto The class used to determine the transformation and validations * @returns The generated class */ -export function FindQueryWhereDtoOf(dto: Type): Type> { +export function FindQueryWhereDtoOf( + dto: Type +): Type> { class WhereDto implements EntityFilterLogicalOperators { @Expose() @IsArray() @@ -193,6 +226,8 @@ export function FindQueryWhereDtoOf(dto: Type): Type { ]; for (const where of wheres) { - const errors = validate(plainToInstance(WhereBooleanDto, where)); + const errors = validate( + plainToInstance(WhereBooleanDto, where) + ); expect(errors).toHaveLength(0); } }); @@ -29,7 +31,9 @@ describe("WhereBooleanDto", () => { const wheres: WhereBooleanNullableDto[] = [{ $eq: null }]; for (const where of wheres) { - const errors = validate(plainToInstance(WhereBooleanNullableDto, where)); + const errors = validate( + plainToInstance(WhereBooleanNullableDto, where) + ); expect(errors).toHaveLength(0); } }); @@ -40,12 +44,21 @@ describe("WhereBooleanDto", () => { [{ $eq: null as unknown as boolean }, 1], // Values are cast in array (when possible) [{ $exists: 2 as unknown as boolean }, 1], - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- For test - [{ $ne: new Date() as unknown as boolean, a: 2 } as WhereBooleanDto, 2] + + [ + { + $ne: new Date() as unknown as boolean, + // @ts-expect-error -- Want an additional property for testing + a: 2 + }, + 2 + ] ]; for (const [where, expectedNError] of toFails) { - const errors = validate(plainToInstance(WhereBooleanDto, where)); + const errors = validate( + plainToInstance(WhereBooleanDto, where) + ); expect(errors).toHaveLength(expectedNError); } }); diff --git a/libs/common/src/dtos/find-query/where/where-boolean.dto.ts b/libs/common/src/dtos/find-query/where/where-boolean.dto.ts index 48e4b5b0..16f4e3bf 100644 --- a/libs/common/src/dtos/find-query/where/where-boolean.dto.ts +++ b/libs/common/src/dtos/find-query/where/where-boolean.dto.ts @@ -44,7 +44,10 @@ const TransformBoolean = (nullable: boolean) => /** * Validation class for nullable `boolean` properties. */ -export class WhereBooleanNullableDto extends WhereBaseDto implements EntityFilterValue { +export class WhereBooleanNullableDto + extends WhereBaseDto + implements EntityFilterValue +{ /** * Search for records whose value is equal to the given one. */ diff --git a/libs/common/src/dtos/find-query/where/where-date.dto.spec.ts b/libs/common/src/dtos/find-query/where/where-date.dto.spec.ts index d645a50a..20182f1f 100644 --- a/libs/common/src/dtos/find-query/where/where-date.dto.spec.ts +++ b/libs/common/src/dtos/find-query/where/where-date.dto.spec.ts @@ -15,7 +15,14 @@ describe("WhereDateDto", () => { it("should be valid", () => { const wheres: WhereDateDto[] = [ - { $eq: date, $gt: date, $gte: date, $lt: date, $lte: date, $ne: date }, + { + $eq: date, + $gt: date, + $gte: date, + $lt: date, + $lte: date, + $ne: date + }, { $in: [date, date], $nin: [date, date] }, { $exists: true, $nin: [] }, { $exists: false } @@ -29,13 +36,22 @@ describe("WhereDateDto", () => { it("should be valid (with nullable)", () => { const wheres: WhereDateNullableDto[] = [ - { $eq: null, $gt: date, $gte: date, $lt: date, $lte: date, $ne: date }, + { + $eq: null, + $gt: date, + $gte: date, + $lt: date, + $lte: date, + $ne: date + }, { $in: [date, date], $ne: null }, { $exists: false } ]; for (const where of wheres) { - const errors = validate(plainToInstance(WhereDateNullableDto, where)); + const errors = validate( + plainToInstance(WhereDateNullableDto, where) + ); expect(errors).toHaveLength(0); } }); @@ -43,7 +59,13 @@ describe("WhereDateDto", () => { it("should not be valid", () => { const toFails: Array<[WhereDateDto, number]> = [ [{ $eq: "a" as unknown as Date }, 1], - [{ $exists: 2 as unknown as boolean, $in: [date, "qff" as unknown as Date] }, 2], + [ + { + $exists: 2 as unknown as boolean, + $in: [date, "qff" as unknown as Date] + }, + 2 + ], // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- For test [{ $ne: 2 as unknown as Date, a: 2 } as WhereDateDto, 2] ]; diff --git a/libs/common/src/dtos/find-query/where/where-date.dto.ts b/libs/common/src/dtos/find-query/where/where-date.dto.ts index 9c23a179..4a126adc 100644 --- a/libs/common/src/dtos/find-query/where/where-date.dto.ts +++ b/libs/common/src/dtos/find-query/where/where-date.dto.ts @@ -8,7 +8,10 @@ import { CanBeNull } from "../../../utils/validations"; /** * Validation class for nullable `Date` properties. */ -export class WhereDateNullableDto extends WhereBaseDto implements EntityFilterValue { +export class WhereDateNullableDto + extends WhereBaseDto + implements EntityFilterValue +{ /** * Search for records whose value is equal to the given one. */ diff --git a/libs/common/src/dtos/find-query/where/where-number.dto.spec.ts b/libs/common/src/dtos/find-query/where/where-number.dto.spec.ts index 31119c84..1c24b781 100644 --- a/libs/common/src/dtos/find-query/where/where-number.dto.spec.ts +++ b/libs/common/src/dtos/find-query/where/where-number.dto.spec.ts @@ -34,7 +34,9 @@ describe("WhereNumberDto", () => { ]; for (const where of wheres) { - const errors = validate(plainToInstance(WhereNumberNullableDto, where)); + const errors = validate( + plainToInstance(WhereNumberNullableDto, where) + ); expect(errors).toHaveLength(0); } }); @@ -44,9 +46,22 @@ describe("WhereNumberDto", () => { [{ $eq: "a" as unknown as number }, 1], [{ $eq: null as unknown as number }, 1], // In an array the string will be cast to number - [{ $exists: 2 as unknown as boolean, $in: [4, "3" as unknown as number] }, 1], - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- For test - [{ $ne: new Date().toISOString() as unknown as number, a: 2 } as WhereNumberDto, 2] + [ + { + $exists: 2 as unknown as boolean, + $in: [4, "3" as unknown as number] + }, + 1 + ], + + [ + { + $ne: new Date().toISOString() as unknown as number, + // @ts-expect-error -- Want an additional property for testing + a: 2 + }, + 2 + ] ]; for (const [where, expectedNError] of toFails) { diff --git a/libs/common/src/dtos/find-query/where/where-number.dto.ts b/libs/common/src/dtos/find-query/where/where-number.dto.ts index e512ebe2..774f1ed7 100644 --- a/libs/common/src/dtos/find-query/where/where-number.dto.ts +++ b/libs/common/src/dtos/find-query/where/where-number.dto.ts @@ -8,7 +8,10 @@ import { CanBeNull } from "../../../utils/validations"; /** * Validation class for nullable `number` properties. */ -export class WhereNumberNullableDto extends WhereBaseDto implements EntityFilterValue { +export class WhereNumberNullableDto + extends WhereBaseDto + implements EntityFilterValue +{ /** * Search for records whose value is equal to the given one. */ diff --git a/libs/common/src/dtos/find-query/where/where-string.dto.spec.ts b/libs/common/src/dtos/find-query/where/where-string.dto.spec.ts index e9fecb40..b8d2b9fd 100644 --- a/libs/common/src/dtos/find-query/where/where-string.dto.spec.ts +++ b/libs/common/src/dtos/find-query/where/where-string.dto.spec.ts @@ -14,7 +14,14 @@ describe("WhereStringDto", () => { it("should be valid", () => { const wheres: WhereStringDto[] = [ - { $eq: "1", $gt: "2", $gte: "3", $lt: "4", $lte: "5", $ne: "6" }, + { + $eq: "1", + $gt: "2", + $gte: "3", + $lt: "4", + $lte: "5", + $ne: "6" + }, { $in: ["7", "8"], $nin: ["9", "0"] }, { $exists: true, $nin: [] }, { $exists: false }, @@ -29,13 +36,22 @@ describe("WhereStringDto", () => { it("should be valid (with nullable)", () => { const wheres: WhereStringNullableDto[] = [ - { $eq: null, $gt: "2", $gte: "3", $lt: "4", $lte: "5", $ne: "6" }, + { + $eq: null, + $gt: "2", + $gte: "3", + $lt: "4", + $lte: "5", + $ne: "6" + }, { $in: ["7", "8"], $ne: null }, { $like: "abc", $re: "def" } ]; for (const where of wheres) { - const errors = validate(plainToInstance(WhereStringNullableDto, where)); + const errors = validate( + plainToInstance(WhereStringNullableDto, where) + ); expect(errors).toHaveLength(0); } }); @@ -45,10 +61,29 @@ describe("WhereStringDto", () => { [{ $eq: 1 as unknown as string }, 1], [{ $eq: null as unknown as string }, 1], // Values are cast in array (when possible) - [{ $exists: 2 as unknown as boolean, $in: ["3", 4 as unknown as string] }, 1], - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- For test - [{ $ne: new Date() as unknown as string, a: 2 } as WhereStringDto, 2], - [{ $like: new Date() as unknown as string, $re: 3 as unknown as string }, 2] + [ + { + $exists: 2 as unknown as boolean, + $in: ["3", 4 as unknown as string] + }, + 1 + ], + + [ + { + $ne: new Date() as unknown as string, + // @ts-expect-error -- Want an additional property for testing + a: 2 + }, + 2 + ], + [ + { + $like: new Date() as unknown as string, + $re: 3 as unknown as string + }, + 2 + ] ]; for (const [where, expectedNError] of toFails) { diff --git a/libs/common/src/dtos/find-query/where/where-string.dto.ts b/libs/common/src/dtos/find-query/where/where-string.dto.ts index 7e528868..f61b26b2 100644 --- a/libs/common/src/dtos/find-query/where/where-string.dto.ts +++ b/libs/common/src/dtos/find-query/where/where-string.dto.ts @@ -8,7 +8,10 @@ import { CanBeNull } from "../../../utils/validations"; /** * Validation class for nullable `string` properties. */ -export class WhereStringNullableDto extends WhereBaseDto implements EntityFilterValue { +export class WhereStringNullableDto + extends WhereBaseDto + implements EntityFilterValue +{ /** * Search for records whose value is equal to the given one. */ diff --git a/libs/common/src/dtos/find-results.dto.ts b/libs/common/src/dtos/find-results.dto.ts index 6a850d73..b00a205e 100644 --- a/libs/common/src/dtos/find-results.dto.ts +++ b/libs/common/src/dtos/find-results.dto.ts @@ -60,7 +60,9 @@ export abstract class FindResultsDto { * @param dto The class to determine the result type * @returns The generated class */ -export function FindResultsDtoOf(dto: Type): Type & { data: T[] }> { +export function FindResultsDtoOf( + dto: Type +): Type & { data: T[] }> { class ResultsDto extends FindResultsDto { @ApiProperty({ isArray: true, type: dto }) public declare data: T[]; diff --git a/libs/common/src/endpoints/entity-filter.types.ts b/libs/common/src/endpoints/entity-filter.types.ts index a38ac6a5..015a2d1c 100644 --- a/libs/common/src/endpoints/entity-filter.types.ts +++ b/libs/common/src/endpoints/entity-filter.types.ts @@ -1,7 +1,10 @@ import { type Collection } from "@mikro-orm/core"; import { ExcludeFunctions, OperatorMap } from "@mikro-orm/core/typings"; -export type EntityFilterValue = Omit, "$and" | "$not" | "$or">; +export type EntityFilterValue = Omit< + OperatorMap, + "$and" | "$not" | "$or" +>; export type EntityFilterObject = { [P in keyof T as ExcludeFunctions]?: T[P] extends Date @@ -17,7 +20,6 @@ export type EntityFilterObject = { : EntityFilterValue | T[P]; }; -/* eslint-disable no-use-before-define -- recursive type */ /** * Possible Logical operators for an entity */ @@ -45,6 +47,6 @@ export interface EntityFilterLogicalOperators { */ $or?: ReadonlyArray>; } -/* eslint-enable */ -export type EntityFilter = EntityFilterLogicalOperators & EntityFilterObject; +export type EntityFilter = EntityFilterLogicalOperators & + EntityFilterObject; diff --git a/libs/common/src/endpoints/entity-order.spec.ts b/libs/common/src/endpoints/entity-order.spec.ts index 720022d6..9b8b8958 100644 --- a/libs/common/src/endpoints/entity-order.spec.ts +++ b/libs/common/src/endpoints/entity-order.spec.ts @@ -1,5 +1,13 @@ -import { isOrderValue, isOrderValueAsc, isOrderValueDesc } from "./entity-order.functions"; -import { OrderValue, OrderValueAsc, OrderValueDesc } from "./entity-order.types"; +import { + isOrderValue, + isOrderValueAsc, + isOrderValueDesc +} from "./entity-order.functions"; +import { + OrderValue, + OrderValueAsc, + OrderValueDesc +} from "./entity-order.types"; describe("EntityOrder", () => { describe("OrderValue", () => { @@ -12,31 +20,47 @@ describe("EntityOrder", () => { "desc_nf", "desc_nl" ] satisfies OrderValue[]) { - expect(isOrderValue(order)).toBeTrue(); + expect(isOrderValue(order)).toBe(true); } for (const order of ["abc", 1, {}]) { - expect(isOrderValue(order)).toBeFalse(); + expect(isOrderValue(order)).toBe(false); } }); it("should determine an `ASC` order", () => { - for (const order of ["asc", "asc_nf", "asc_nl"] satisfies OrderValueAsc[]) { - expect(isOrderValueAsc(order)).toBeTrue(); + for (const order of [ + "asc", + "asc_nf", + "asc_nl" + ] satisfies OrderValueAsc[]) { + expect(isOrderValueAsc(order)).toBe(true); } - for (const order of ["desc", "desc_nf", "desc_nl"] satisfies OrderValueDesc[]) { - expect(isOrderValueAsc(order)).toBeFalse(); + for (const order of [ + "desc", + "desc_nf", + "desc_nl" + ] satisfies OrderValueDesc[]) { + expect(isOrderValueAsc(order)).toBe(false); } }); it("should determine an `DESC` order", () => { - for (const order of ["asc", "asc_nf", "asc_nl"] satisfies OrderValueAsc[]) { - expect(isOrderValueDesc(order)).toBeFalse(); + for (const order of [ + "asc", + "asc_nf", + "asc_nl" + ] satisfies OrderValueAsc[]) { + expect(isOrderValueDesc(order)).toBe(false); } - for (const order of ["desc", "desc_nf", "desc_nl"] satisfies OrderValueDesc[]) { - expect(isOrderValueDesc(order)).toBeTrue(); + for (const order of [ + "desc", + "desc_nf", + "desc_nl" + ] satisfies OrderValueDesc[]) { + expect(isOrderValueDesc(order)).toBe(true); } }); }); diff --git a/libs/common/src/endpoints/entity-populated.types.ts b/libs/common/src/endpoints/entity-populated.types.ts index 002a1c48..fc6d2953 100644 --- a/libs/common/src/endpoints/entity-populated.types.ts +++ b/libs/common/src/endpoints/entity-populated.types.ts @@ -39,7 +39,10 @@ export type EntitiesToPopulate = { /** * The entity with its loaded nested entities */ -export type EntityPopulated = never> = T & { +export type EntityPopulated< + T extends EntityDto, + P extends EntitiesToPopulate = never +> = T & { [K in keyof P & keyof T]: P[K] extends true ? Required[K] : // Nested entities (can still be `null` for nullable relations) @@ -49,9 +52,17 @@ export type EntityPopulated : U : // Mikro-orm Collection NonNullable extends Collection - ? Collection ? EntityPopulated & U : U> + ? Collection< + P[K] extends EntitiesToPopulate + ? EntityPopulated & U + : U + > : // DTO arrays NonNullable extends Array - ? Array ? EntityPopulated & U : U> + ? Array< + P[K] extends EntitiesToPopulate + ? EntityPopulated & U + : U + > : never; }; diff --git a/libs/common/src/http/http.method.spec.ts b/libs/common/src/http/http.method.spec.ts index eeac10fe..84f4af6c 100644 --- a/libs/common/src/http/http.method.spec.ts +++ b/libs/common/src/http/http.method.spec.ts @@ -7,15 +7,21 @@ describe("HTTP methods", () => { methods.push(...methods.map(method => method.toLowerCase())); for (const method of methods) { - expect(isAHttpMethod(method)).toBeTrue(); + expect(isAHttpMethod(method)).toBe(true); } }); it("should return that a string is not a HTTP method", () => { - const methods: string[] = ["UPDATE", "got", "Post", "REMOVE", "create"]; + const methods: string[] = [ + "UPDATE", + "got", + "Post", + "REMOVE", + "create" + ]; for (const method of methods) { - expect(isAHttpMethod(method)).toBeFalse(); + expect(isAHttpMethod(method)).toBe(false); } }); }); diff --git a/libs/common/src/http/http.method.ts b/libs/common/src/http/http.method.ts index c07bd5c8..6b7235a0 100644 --- a/libs/common/src/http/http.method.ts +++ b/libs/common/src/http/http.method.ts @@ -11,7 +11,11 @@ export type HttpMethod = HttpMethodUpperCase | Lowercase; const allMethods: readonly HttpMethod[] = ( ["DELETE", "GET", "PATCH", "POST", "PUT"] satisfies HttpMethod[] ).reduce( - (methods, method) => [...methods, method, method.toLowerCase() as HttpMethod], + (methods, method) => [ + ...methods, + method, + method.toLowerCase() as HttpMethod + ], [] ); diff --git a/libs/common/src/http/query/http-query.decode.ts b/libs/common/src/http/query/http-query.decode.ts index 27cefd4f..c3f521e0 100644 --- a/libs/common/src/http/query/http-query.decode.ts +++ b/libs/common/src/http/query/http-query.decode.ts @@ -66,7 +66,7 @@ export function httpQueryDecode( ) { if (typeof query === "string") { // The real value if it has a prefix - const value = query.substring(1); + const value = query.slice(1); switch (query.charAt(0) as QueryPrefixIdentifier) { case QueryPrefixIdentifier.DATE: diff --git a/libs/common/src/http/query/http-query.encode.ts b/libs/common/src/http/query/http-query.encode.ts index 79fe2664..cdee583b 100644 --- a/libs/common/src/http/query/http-query.encode.ts +++ b/libs/common/src/http/query/http-query.encode.ts @@ -64,7 +64,11 @@ export function httpQueryEncode( query: ReadonlyDeep, options?: ReadonlyDeep ): QueryEncoded { - if (([true, false, null, undefined] satisfies QueryPrimitiveValue[]).includes(query as never)) { + if ( + ( + [true, false, null, undefined] satisfies QueryPrimitiveValue[] + ).includes(query as never) + ) { return `${QueryPrefixIdentifier.PRIMITIVE}${query as string}`; } if (typeof query === "number") { @@ -74,7 +78,9 @@ export function httpQueryEncode( return `${QueryPrefixIdentifier.DATE}${query.toISOString()}`; } if (query instanceof RegExp) { - return `${QueryPrefixIdentifier.REG_EXP}${query.toString().slice(1, -1)}`; + return `${QueryPrefixIdentifier.REG_EXP}${query + .toString() + .slice(1, -1)}`; } if (typeof query === "string") { diff --git a/libs/common/src/http/query/http-query.spec.ts b/libs/common/src/http/query/http-query.spec.ts index eebacaa8..0562ed93 100644 --- a/libs/common/src/http/query/http-query.spec.ts +++ b/libs/common/src/http/query/http-query.spec.ts @@ -112,8 +112,12 @@ describe("HTTP Query transformer", () => { const encodedComplex = httpQueryEncode(complex); expect(encodedComplex.flatArray[0]).not.toBe(complex.flatArray[0]); - expect(encodedComplex.nested.nestedA.a).not.toBe(complex.nested.nestedA.a); - expect(encodedComplex.nested.nestedB.b).not.toBe(complex.nested.nestedB.b); + expect(encodedComplex.nested.nestedA.a).not.toBe( + complex.nested.nestedA.a + ); + expect(encodedComplex.nested.nestedB.b).not.toBe( + complex.nested.nestedB.b + ); expect(encodedComplex.nestedArray.nestedA[1].b).not.toBe( complex.nestedArray.nestedA[1].b ); @@ -122,7 +126,9 @@ describe("HTTP Query transformer", () => { ); expect(encodedComplex.value).not.toBe(complex.value); - expect(encodedComplex.nestedArray.nestedA[0]).toBe(complex.nestedArray.nestedA[0]); + expect(encodedComplex.nestedArray.nestedA[0]).toBe( + complex.nestedArray.nestedA[0] + ); }); }); }); diff --git a/libs/common/src/http/query/http-query.types.ts b/libs/common/src/http/query/http-query.types.ts index fe70916f..47c0f0cf 100644 --- a/libs/common/src/http/query/http-query.types.ts +++ b/libs/common/src/http/query/http-query.types.ts @@ -71,5 +71,8 @@ export type QueryEncoded = T extends QueryValue * An object that is encoded. */ export interface QueryEncodedObject { - [key: string]: Array | QueryEncodedObject | string; + [key: string]: + | Array + | QueryEncodedObject + | string; } diff --git a/libs/common/src/seeds/base.seed.ts b/libs/common/src/seeds/base.seed.ts index abf6a684..720ecddf 100644 --- a/libs/common/src/seeds/base.seed.ts +++ b/libs/common/src/seeds/base.seed.ts @@ -178,7 +178,11 @@ export const BASE_SEED = { __categories: [1], behavior: { type: NodeBehaviorType.VARIABLE, value: "host" }, - kind: { __graph: 2, position: { x: 25, y: 25 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 2, + position: { x: 25, y: 25 }, + type: NodeKindType.VERTEX + }, name: "DB_HOST", inputs: [ @@ -217,7 +221,11 @@ export const BASE_SEED = { __categories: [1], behavior: { type: NodeBehaviorType.VARIABLE, value: 1234 }, - kind: { __graph: 2, position: { x: 25, y: 150 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 2, + position: { x: 25, y: 150 }, + type: NodeKindType.VERTEX + }, name: "DB_PORT", inputs: [ @@ -256,7 +264,11 @@ export const BASE_SEED = { __categories: [1], behavior: { type: NodeBehaviorType.VARIABLE, value: "user" }, - kind: { __graph: 2, position: { x: 25, y: 275 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 2, + position: { x: 25, y: 275 }, + type: NodeKindType.VERTEX + }, name: "DB_USER", inputs: [ @@ -295,7 +307,11 @@ export const BASE_SEED = { __categories: [1], behavior: { type: NodeBehaviorType.VARIABLE, value: "pass" }, - kind: { __graph: 2, position: { x: 25, y: 400 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 2, + position: { x: 25, y: 400 }, + type: NodeKindType.VERTEX + }, name: "DB_PASS", inputs: [ @@ -337,8 +353,15 @@ export const BASE_SEED = { __categories: [1, 2], - behavior: { code: "module.export = console.log", type: NodeBehaviorType.CODE }, - kind: { __graph: 2, position: { x: 600, y: 250 }, type: NodeKindType.VERTEX }, + behavior: { + code: "module.export = console.log", + type: NodeBehaviorType.CODE + }, + kind: { + __graph: 2, + position: { x: 600, y: 250 }, + type: NodeKindType.VERTEX + }, name: "Make SQL query", inputs: [ @@ -597,8 +620,15 @@ export const BASE_SEED = { __categories: [], - behavior: { __node_input: 2001, type: NodeBehaviorType.PARAMETER_IN }, - kind: { __graph: 1, position: { x: 25, y: 125 }, type: NodeKindType.VERTEX }, + behavior: { + __node_input: 2001, + type: NodeBehaviorType.PARAMETER_IN + }, + kind: { + __graph: 1, + position: { x: 25, y: 125 }, + type: NodeKindType.VERTEX + }, name: "Dividend", inputs: [], @@ -624,8 +654,15 @@ export const BASE_SEED = { __categories: [], - behavior: { __node_input: 2002, type: NodeBehaviorType.PARAMETER_IN }, - kind: { __graph: 1, position: { x: 25, y: 250 }, type: NodeKindType.VERTEX }, + behavior: { + __node_input: 2002, + type: NodeBehaviorType.PARAMETER_IN + }, + kind: { + __graph: 1, + position: { x: 25, y: 250 }, + type: NodeKindType.VERTEX + }, name: "Divisor", inputs: [], @@ -651,8 +688,15 @@ export const BASE_SEED = { __categories: [], - behavior: { __node_output: 2003, type: NodeBehaviorType.PARAMETER_OUT }, - kind: { __graph: 1, position: { x: 725, y: 125 }, type: NodeKindType.VERTEX }, + behavior: { + __node_output: 2003, + type: NodeBehaviorType.PARAMETER_OUT + }, + kind: { + __graph: 1, + position: { x: 725, y: 125 }, + type: NodeKindType.VERTEX + }, name: "Quotient", inputs: [ @@ -678,8 +722,15 @@ export const BASE_SEED = { __categories: [], - behavior: { __node_output: 2004, type: NodeBehaviorType.PARAMETER_OUT }, - kind: { __graph: 1, position: { x: 725, y: 250 }, type: NodeKindType.VERTEX }, + behavior: { + __node_output: 2004, + type: NodeBehaviorType.PARAMETER_OUT + }, + kind: { + __graph: 1, + position: { x: 725, y: 250 }, + type: NodeKindType.VERTEX + }, name: "Remainder", inputs: [ @@ -713,7 +764,11 @@ export const BASE_SEED = { trigger: { cron: "1 * * * *", type: NodeTriggerType.CRON }, type: NodeBehaviorType.TRIGGER }, - kind: { __graph: 2, position: { x: 50, y: 650 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 2, + position: { x: 50, y: 650 }, + type: NodeKindType.VERTEX + }, name: "Cron", inputs: [], @@ -743,7 +798,11 @@ export const BASE_SEED = { __categories: [2], behavior: { __node: 10, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 325, y: 25 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 1, + position: { x: 325, y: 25 }, + type: NodeKindType.VERTEX + }, name: "Calculate quotient (reference)", inputs: [ @@ -795,7 +854,11 @@ export const BASE_SEED = { __categories: [2], behavior: { __node: 11, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 325, y: 250 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 1, + position: { x: 325, y: 250 }, + type: NodeKindType.VERTEX + }, name: "Calculate remainder (reference)", inputs: [ diff --git a/libs/common/src/seeds/only-nodes.seed.ts b/libs/common/src/seeds/only-nodes.seed.ts index a8444c82..6e005cd9 100644 --- a/libs/common/src/seeds/only-nodes.seed.ts +++ b/libs/common/src/seeds/only-nodes.seed.ts @@ -180,8 +180,15 @@ export const ONLY_NODES_SEED = { __categories: [], - behavior: { __node_input: 4, type: NodeBehaviorType.PARAMETER_IN }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node_input: 4, + type: NodeBehaviorType.PARAMETER_IN + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "PARAMETER_IN", inputs: [], @@ -207,8 +214,15 @@ export const ONLY_NODES_SEED = { __categories: [], - behavior: { __node_output: 4, type: NodeBehaviorType.PARAMETER_OUT }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + behavior: { + __node_output: 4, + type: NodeBehaviorType.PARAMETER_OUT + }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "PARAMETER_OUT", inputs: [ @@ -235,7 +249,11 @@ export const ONLY_NODES_SEED = { __categories: [], behavior: { __node: 2, type: NodeBehaviorType.REFERENCE }, - kind: { __graph: 1, position: { x: 0, y: 0 }, type: NodeKindType.VERTEX }, + kind: { + __graph: 1, + position: { x: 0, y: 0 }, + type: NodeKindType.VERTEX + }, name: "REFERENCE", inputs: [ diff --git a/libs/common/src/types/dot.path.ts b/libs/common/src/types/dot.path.ts index ca328755..e28e0086 100644 --- a/libs/common/src/types/dot.path.ts +++ b/libs/common/src/types/dot.path.ts @@ -7,8 +7,11 @@ export type AllKeysOf = T extends T ? keyof T : never; // Inspired from // https://www.typescriptlang.org/play?ts=4.1.0-dev.20200921#code/C4TwDgpgBACghsAFgSQLZgDYB4AqAaKAaSggA9gIA7AEwGcoBrCEAewDMocA+KAXgCgoREuSp0otYACcAlpQDmgqAH5OAbUIBdERRr0AShADGLKdSyTZCgnEoguSoapwbtZXeICCUqXBAAZGSYsW3tHIRVhAB8oAAMAEgBvQgBfADok+CQ0TFxXAgBRUiMMAFdqCCwmVg4XLQJq9ihQtU0uLhTY8KEALmi4pNSMxKyUdGw6zQbmJsmOroioPsIlPsoIADcIKQBufn5QSFgERFweXmPs8dxpms4eGMbavYPwaFGANTgyyvxYHTE9FGZ3OShgAL0A0ScjY2yI6SSMLhhkknSUqmI7kBjBmtXCqhRwAh4mBc26kU+31Kv3yUEJDkWvSg6y2UnCa0221W-yxkKenHx6hgmnZzM5u32FRKcCk0DYpUoRmAMhYlCg8ggwBuPNEkNJ7QAFCwAEYAKz6fzAJz6MAAlDaTl8ftqYFwXkA type DotPathImpl = K extends string - ? // eslint-disable-next-line no-use-before-define -- Circular type - K | (NonNullable extends infer U | null ? `${K}.${DotPath>}` : never) + ? + | K + | (NonNullable extends infer U | null + ? `${K}.${DotPath>}` + : never) : never; /** diff --git a/libs/common/src/types/function.ts b/libs/common/src/types/function.ts index ff4721c3..cb4cb663 100644 --- a/libs/common/src/types/function.ts +++ b/libs/common/src/types/function.ts @@ -10,6 +10,8 @@ export type UnshiftParameters = FN extends ( /** * Change a function type by adding parameters at the end of the exiting ones */ -export type PushParameters = FN extends (...args: infer P) => infer R +export type PushParameters = FN extends ( + ...args: infer P +) => infer R ? (...args: [...P, ...PARAMS]) => R : never; diff --git a/libs/common/src/types/orm.ts b/libs/common/src/types/orm.ts index db4537e3..fcdfbddd 100644 --- a/libs/common/src/types/orm.ts +++ b/libs/common/src/types/orm.ts @@ -3,6 +3,5 @@ import { Collection } from "@mikro-orm/core"; /** * Extract the type of a Mikro-orm collection */ -export type EntityFromCollection> = T extends Collection - ? U - : never; +export type EntityFromCollection> = + T extends Collection ? U : never; diff --git a/libs/common/src/types/types.ts b/libs/common/src/types/types.ts index b6f1eb02..a8f60227 100644 --- a/libs/common/src/types/types.ts +++ b/libs/common/src/types/types.ts @@ -5,7 +5,9 @@ import { Primitive as RealPrimitive } from "type-fest"; * * @see https://stackoverflow.com/a/63029283 */ -export type DropFirst = T extends [unknown, ...infer U] ? U : never; +export type DropFirst = T extends [unknown, ...infer U] + ? U + : never; /** * The primitives, by this project point of view. diff --git a/libs/common/src/utils/object-fns/omit.spec.ts b/libs/common/src/utils/object-fns/omit.spec.ts index 3f75fa4c..c1903ece 100644 --- a/libs/common/src/utils/object-fns/omit.spec.ts +++ b/libs/common/src/utils/object-fns/omit.spec.ts @@ -27,7 +27,7 @@ describe("object-fns `omit`", () => { expect(kept).toHaveLength(all.length - toRemove.length); expect(kept).toHaveLength(mustBeKept.length); - expect(mustBeKept.every(key => kept.includes(key))).toBeTrue(); + expect(mustBeKept.every(key => kept.includes(key))).toBe(true); } }); @@ -36,6 +36,8 @@ describe("object-fns `omit`", () => { expect(omit(ref, ["p1", "p2", "p3"])).toStrictEqual({}); expect(omit(ref, ["p1", "p2", "p3", "p4", "p5"])).toStrictEqual({}); expect(omit(ref, ["p3", "p4", "p5"])).toStrictEqual({ p1: 0, p2: 0 }); - expect(omit(ref, ["p1", "p2", "p3", "p4", "p0" as Key])).toStrictEqual({}); + expect(omit(ref, ["p1", "p2", "p3", "p4", "p0" as Key])).toStrictEqual( + {} + ); }); }); diff --git a/libs/common/src/utils/object-fns/pick.spec.ts b/libs/common/src/utils/object-fns/pick.spec.ts index 977225d1..5c3ddafe 100644 --- a/libs/common/src/utils/object-fns/pick.spec.ts +++ b/libs/common/src/utils/object-fns/pick.spec.ts @@ -23,14 +23,22 @@ describe("object-fns `pick`", () => { // The number of keys should be different expect(picked).toHaveLength(toPick.length); - expect(toPick.every(key => picked.includes(key))).toBeTrue(); + expect(toPick.every(key => picked.includes(key))).toBe(true); } }); it("should be ok with optional (and unknown) keys", () => { const ref: Struct = { p1: 0, p2: 0, p3: 0 }; - expect(pick(ref, ["p1", "p2", "p3"])).toStrictEqual({ p1: 0, p2: 0, p3: 0 }); - expect(pick(ref, ["p1", "p2", "p3", "p4", "p5"])).toStrictEqual({ p1: 0, p2: 0, p3: 0 }); + expect(pick(ref, ["p1", "p2", "p3"])).toStrictEqual({ + p1: 0, + p2: 0, + p3: 0 + }); + expect(pick(ref, ["p1", "p2", "p3", "p4", "p5"])).toStrictEqual({ + p1: 0, + p2: 0, + p3: 0 + }); expect(pick(ref, ["p3", "p4", "p5"])).toStrictEqual({ p3: 0 }); expect(pick(ref, ["p1", "p0" as Key])).toStrictEqual({ p1: 0 }); }); diff --git a/libs/common/src/utils/validations/can-be-null.decorator.ts b/libs/common/src/utils/validations/can-be-null.decorator.ts index bc2fde00..f305e256 100644 --- a/libs/common/src/utils/validations/can-be-null.decorator.ts +++ b/libs/common/src/utils/validations/can-be-null.decorator.ts @@ -18,6 +18,8 @@ import "reflect-metadata"; * @param validationOptions The additional options for the validation * @returns The decorator */ -export function CanBeNull(validationOptions?: ValidationOptions): PropertyDecorator { +export function CanBeNull( + validationOptions?: ValidationOptions +): PropertyDecorator { return ValidateIf((_, value) => value !== null, validationOptions); } diff --git a/libs/common/src/validators/is-cron.spec.ts b/libs/common/src/validators/is-cron.spec.ts index c18f1d75..59d8e0cb 100644 --- a/libs/common/src/validators/is-cron.spec.ts +++ b/libs/common/src/validators/is-cron.spec.ts @@ -23,13 +23,17 @@ describe("IsCron", () => { { cron: "1 2 3 4 5" }, { cron: "* 10/12 * * *" } ] satisfies TestRef[]) { - expect(validate(test)).toBeTrue(); + expect(validate(test)).toBe(true); } }); it("should not be valid", () => { - for (const test of [{ cron: 1 }, { cron: "* * * *" }, { cron: null }] satisfies TestRef[]) { - expect(validate(test)).toBeFalse(); + for (const test of [ + { cron: 1 }, + { cron: "* * * *" }, + { cron: null } + ] satisfies TestRef[]) { + expect(validate(test)).toBe(false); } }); }); diff --git a/libs/common/src/validators/is-cron.ts b/libs/common/src/validators/is-cron.ts index 048fe439..16edd5cc 100644 --- a/libs/common/src/validators/is-cron.ts +++ b/libs/common/src/validators/is-cron.ts @@ -28,7 +28,9 @@ export function IsCron(validationOptions?: ValidationOptions) { { name: IS_CRON, validator: { - defaultMessage: buildMessage(prefix => `${prefix}$property must be a valid cron`), + defaultMessage: buildMessage( + prefix => `${prefix}$property must be a valid cron` + ), validate: isCron } }, diff --git a/libs/common/tsconfig.spec.json b/libs/common/tsconfig.spec.json index 938a3821..7a24ac95 100644 --- a/libs/common/tsconfig.spec.json +++ b/libs/common/tsconfig.spec.json @@ -6,10 +6,10 @@ }, "extends": "./tsconfig.json", "include": [ - "src/**/*.test.ts", + "src/**/*.d.ts", + "src/**/*.spec.js", "src/**/*.spec.ts", "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.d.ts" + "src/**/*.test.ts" ] } diff --git a/libs/ng/.eslintrc.json b/libs/ng/.eslintrc.json index b74f7c30..5fc1aad3 100644 --- a/libs/ng/.eslintrc.json +++ b/libs/ng/.eslintrc.json @@ -3,9 +3,9 @@ "overrides": [ { "extends": [ - "plugin:@nx/angular", "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:@nx/angular" ], "files": ["*.ts"], "parserOptions": { @@ -16,7 +16,13 @@ "@angular-eslint/component-class-suffix": [ "error", { - "suffixes": ["Component", "Card", "Dialog", "SnackBar", "View"] + "suffixes": [ + "Component", + "Card", + "Dialog", + "SnackBar", + "View" + ] } ], "@angular-eslint/component-selector": [ @@ -65,9 +71,9 @@ }, { "extends": [ - "plugin:@nx/angular-template", "plugin:@angular-eslint/template/accessibility", - "plugin:@angular-eslint/template/recommended" + "plugin:@angular-eslint/template/recommended", + "plugin:@nx/angular-template" ], "files": ["*.html"], "plugins": ["prettier", "sort-attribute-content"], @@ -87,6 +93,7 @@ "prettier/prettier": [ "error", { + "htmlWhitespaceSensitivity": "strict", "parser": "angular" } ], diff --git a/libs/ng/src/lib/api/_lib/entity-api/entity-api.service.ts b/libs/ng/src/lib/api/_lib/entity-api/entity-api.service.ts index 5b2508d1..be3107a7 100644 --- a/libs/ng/src/lib/api/_lib/entity-api/entity-api.service.ts +++ b/libs/ng/src/lib/api/_lib/entity-api/entity-api.service.ts @@ -3,7 +3,10 @@ import stringify from "qs/lib/stringify"; import { Jsonify } from "type-fest"; import { EntityDto, EntityId } from "~/lib/common/dtos/entity"; import { DtoToEntity } from "~/lib/common/dtos/entity/entity.types"; -import { EntityFindQuery, EntityFindResult } from "~/lib/common/endpoints/entity-find.interfaces"; +import { + EntityFindQuery, + EntityFindResult +} from "~/lib/common/endpoints/entity-find.interfaces"; import { EntityEndpoint } from "~/lib/common/endpoints/entity.endpoint"; import { ApiClient } from "../../api.client"; @@ -55,7 +58,10 @@ export abstract class EntityApiService< * @returns The results of the request */ public findAndCount(query?: Q): Promise> { - return this.findAndCountFromParams({ uri: this.getEntrypoint() }, query); + return this.findAndCountFromParams( + { uri: this.getEntrypoint() }, + query + ); } /** @@ -109,10 +115,10 @@ export abstract class EntityApiService< * @param query to request * @returns the data found and its total */ - protected findAndCountFromParams = EntityFindQuery>( - params: FindAndCountParams, - query?: Q2 - ): Promise> { + protected findAndCountFromParams< + T2 = T, + Q2 extends EntityFindQuery = EntityFindQuery + >(params: FindAndCountParams, query?: Q2): Promise> { let { uri } = params; if (query) { diff --git a/libs/ng/src/lib/api/api.client.ts b/libs/ng/src/lib/api/api.client.ts index 3af3f643..00ec3972 100644 --- a/libs/ng/src/lib/api/api.client.ts +++ b/libs/ng/src/lib/api/api.client.ts @@ -99,7 +99,11 @@ export class ApiClient { * @param options options of the request like headers, body, etc. * @returns the response of the request */ - public post(endpoint: string, body?: U, options?: RequestOptions): Promise { + public post( + endpoint: string, + body?: U, + options?: RequestOptions + ): Promise { return this.request("POST", endpoint, { ...options, body }); } @@ -111,7 +115,11 @@ export class ApiClient { * @param options options of the request like headers, body, etc. * @returns the response of the request */ - public patch(endpoint: string, body?: U, options?: RequestOptions): Promise { + public patch( + endpoint: string, + body?: U, + options?: RequestOptions + ): Promise { return this.request("PATCH", endpoint, { ...options, body }); } @@ -123,7 +131,11 @@ export class ApiClient { * @param options options of the request like headers, body, etc. * @returns the response of the request */ - public put(endpoint: string, body?: U, options?: RequestOptions): Promise { + public put( + endpoint: string, + body?: U, + options?: RequestOptions + ): Promise { return this.request("PUT", endpoint, { ...options, body }); } @@ -160,7 +172,9 @@ export class ApiClient { if (observeEvent) { // No need to subscribe as `lastValueForm` already does it - request = request.pipe>(tap(value => observeEvent(value))); + request = request.pipe>( + tap(value => observeEvent(value)) + ); } return lastValueFrom(request).then(last => diff --git a/libs/ng/src/lib/api/api.module.ts b/libs/ng/src/lib/api/api.module.ts index 017fd748..f9d8cc6d 100644 --- a/libs/ng/src/lib/api/api.module.ts +++ b/libs/ng/src/lib/api/api.module.ts @@ -1,6 +1,10 @@ import { ModuleWithProviders, NgModule } from "@angular/core"; -import { API_CLIENT_CONFIG_TOKEN, ApiClient, ApiClientConfig } from "./api.client"; +import { + API_CLIENT_CONFIG_TOKEN, + ApiClient, + ApiClientConfig +} from "./api.client"; import { AuthApiService } from "./auth-api"; import { CategoryApiService } from "./category-api"; import { GraphApiService } from "./graph-api"; @@ -27,10 +31,14 @@ export interface ApiModuleConfig { ] }) export class ApiModule { - public static forRoot(config: ApiModuleConfig): ModuleWithProviders { + public static forRoot( + config: ApiModuleConfig + ): ModuleWithProviders { return { ngModule: ApiModule, - providers: [{ provide: API_CLIENT_CONFIG_TOKEN, useValue: config.client }] + providers: [ + { provide: API_CLIENT_CONFIG_TOKEN, useValue: config.client } + ] }; } } diff --git a/libs/ng/src/lib/api/auth-api/auth-api.service.ts b/libs/ng/src/lib/api/auth-api/auth-api.service.ts index 0c702c0f..5bd6e671 100644 --- a/libs/ng/src/lib/api/auth-api/auth-api.service.ts +++ b/libs/ng/src/lib/api/auth-api/auth-api.service.ts @@ -1,6 +1,14 @@ import { Injectable } from "@angular/core"; -import { AuthLoginDto, AuthRefreshDto, AuthSuccessDto } from "~/lib/common/app/auth/dtos"; -import { AUTH_ENDPOINT_PREFIX, AuthEndpoint, AuthEndpoints } from "~/lib/common/app/auth/endpoints"; +import { + AuthLoginDto, + AuthRefreshDto, + AuthSuccessDto +} from "~/lib/common/app/auth/dtos"; +import { + AUTH_ENDPOINT_PREFIX, + AuthEndpoint, + AuthEndpoints +} from "~/lib/common/app/auth/endpoints"; import { UserDto } from "~/lib/common/app/user/dtos"; import { ApiClient } from "../api.client"; @@ -29,7 +37,10 @@ export class AuthApiService implements AuthEndpoint { /** @inheritDoc */ public login(body: AuthLoginDto): Promise { - return this.client.post(`${this.entrypoint}/${AuthEndpoints.LOGIN}`, body); + return this.client.post( + `${this.entrypoint}/${AuthEndpoints.LOGIN}`, + body + ); } /** @inheritDoc */ @@ -39,6 +50,9 @@ export class AuthApiService implements AuthEndpoint { /** @inheritDoc */ public refresh(body: AuthRefreshDto): Promise { - return this.client.post(`${this.entrypoint}/${AuthEndpoints.REFRESH}`, body); + return this.client.post( + `${this.entrypoint}/${AuthEndpoints.REFRESH}`, + body + ); } } diff --git a/libs/ng/src/lib/api/category-api/category.api.service.ts b/libs/ng/src/lib/api/category-api/category.api.service.ts index 64b61875..8943a490 100644 --- a/libs/ng/src/lib/api/category-api/category.api.service.ts +++ b/libs/ng/src/lib/api/category-api/category.api.service.ts @@ -1,5 +1,8 @@ import { Injectable } from "@angular/core"; -import { CategoryCreateDto, CategoryUpdateDto } from "~/lib/common/app/category/dtos"; +import { + CategoryCreateDto, + CategoryUpdateDto +} from "~/lib/common/app/category/dtos"; import { CATEGORIES_ENDPOINT_PREFIX, CategoryJSON, diff --git a/libs/ng/src/lib/api/graph-api/node/graph-node.api.ts b/libs/ng/src/lib/api/graph-api/node/graph-node.api.ts index 1b91902d..ffebb1af 100644 --- a/libs/ng/src/lib/api/graph-api/node/graph-node.api.ts +++ b/libs/ng/src/lib/api/graph-api/node/graph-node.api.ts @@ -1,17 +1,18 @@ import { GraphNodeUpdateDto } from "~/lib/common/app/graph/dtos/node"; import { GraphNodeCreateDto } from "~/lib/common/app/graph/dtos/node/graph-node.create.dto"; -import { generateGraphNodesEndpoint, GraphNodeJSON } from "~/lib/common/app/graph/endpoints"; +import { + generateGraphNodesEndpoint, + GraphNodeJSON +} from "~/lib/common/app/graph/endpoints"; import { NodeJSON } from "~/lib/common/app/node/endpoints"; import { EntityId } from "~/lib/common/dtos/entity"; import { EntityApiService } from "../../_lib/entity-api"; import { ApiClient } from "../../api.client"; -export class GraphNodeApi extends EntityApiService< - T, - GraphNodeCreateDto, - GraphNodeUpdateDto -> { +export class GraphNodeApi< + T extends GraphNodeJSON | NodeJSON = NodeJSON +> extends EntityApiService { /** @inheritDoc */ private readonly endpoint: string; diff --git a/libs/ng/src/lib/api/node-api/node.api.service.ts b/libs/ng/src/lib/api/node-api/node.api.service.ts index 82740eb7..68cccbed 100644 --- a/libs/ng/src/lib/api/node-api/node.api.service.ts +++ b/libs/ng/src/lib/api/node-api/node.api.service.ts @@ -1,6 +1,10 @@ import { Injectable } from "@angular/core"; import { NodeCreateDto, NodeUpdateDto } from "~/lib/common/app/node/dtos"; -import { NodeJSON, NodeEndpoint, NODES_ENDPOINT_PREFIX } from "~/lib/common/app/node/endpoints"; +import { + NodeJSON, + NodeEndpoint, + NODES_ENDPOINT_PREFIX +} from "~/lib/common/app/node/endpoints"; import { EntityApiService } from "../_lib/entity-api"; diff --git a/libs/ng/src/lib/api/user-api/user.api.service.ts b/libs/ng/src/lib/api/user-api/user.api.service.ts index 1511a8a1..fc54860d 100644 --- a/libs/ng/src/lib/api/user-api/user.api.service.ts +++ b/libs/ng/src/lib/api/user-api/user.api.service.ts @@ -1,6 +1,10 @@ import { Injectable } from "@angular/core"; import { UserCreateDto, UserUpdateDto } from "~/lib/common/app/user/dtos"; -import { UserJSON, UserEndpoint, USERS_ENDPOINT_PREFIX } from "~/lib/common/app/user/endpoints"; +import { + UserJSON, + UserEndpoint, + USERS_ENDPOINT_PREFIX +} from "~/lib/common/app/user/endpoints"; import { EntityApiService } from "../_lib/entity-api"; diff --git a/libs/ng/src/lib/api/workflow-api/workflow.api.service.ts b/libs/ng/src/lib/api/workflow-api/workflow.api.service.ts index 040b0892..908e78ff 100644 --- a/libs/ng/src/lib/api/workflow-api/workflow.api.service.ts +++ b/libs/ng/src/lib/api/workflow-api/workflow.api.service.ts @@ -1,6 +1,9 @@ import { Injectable } from "@angular/core"; import { GraphJSON } from "~/lib/common/app/graph/endpoints"; -import { WorkflowCreateDto, WorkflowUpdateDto } from "~/lib/common/app/workflow/dtos"; +import { + WorkflowCreateDto, + WorkflowUpdateDto +} from "~/lib/common/app/workflow/dtos"; import { WorkflowJSON, WORKFLOW_LOOK_FOR_GRAPH_ENDPOINT, @@ -25,6 +28,8 @@ export class WorkflowApiService /** @inheritDoc */ public lookForGraph(id: EntityId): Promise { - return this.client.get(`${this.getEntrypoint()}/${id}${WORKFLOW_LOOK_FOR_GRAPH_ENDPOINT}`); + return this.client.get( + `${this.getEntrypoint()}/${id}${WORKFLOW_LOOK_FOR_GRAPH_ENDPOINT}` + ); } } diff --git a/libs/ng/src/lib/components/loading-box/loading-box.component.html b/libs/ng/src/lib/components/loading-box/loading-box.component.html index e11bafd1..4885fb78 100644 --- a/libs/ng/src/lib/components/loading-box/loading-box.component.html +++ b/libs/ng/src/lib/components/loading-box/loading-box.component.html @@ -1 +1,5 @@ - + diff --git a/libs/ng/src/lib/directives/mat-cell-def.directive.ts b/libs/ng/src/lib/directives/mat-cell-def.directive.ts index 6ebb2139..ddb0ac28 100644 --- a/libs/ng/src/lib/directives/mat-cell-def.directive.ts +++ b/libs/ng/src/lib/directives/mat-cell-def.directive.ts @@ -10,7 +10,7 @@ import { Observable } from "rxjs"; */ @Directive({ // same selector as MatCellDef - // eslint-disable-next-line no-use-before-define -- needed + providers: [{ provide: CdkCellDef, useExisting: MatCellDefDirective }], // eslint-disable-next-line @angular-eslint/directive-selector -- Need to be the same selector: "[matCellDef]", diff --git a/libs/ng/src/lib/forms/form-controls.types.ts b/libs/ng/src/lib/forms/form-controls.types.ts index 7b9eedc5..ebaf4bbc 100644 --- a/libs/ng/src/lib/forms/form-controls.types.ts +++ b/libs/ng/src/lib/forms/form-controls.types.ts @@ -3,9 +3,13 @@ import { FormControl } from "@angular/forms"; /** * Transforms (on a single level) any object to its FormControls */ -export type FormControlsFrom = { [K in keyof Required]: FormControl }; +export type FormControlsFrom = { + [K in keyof Required]: FormControl; +}; /** * Same as {@link FormControlsFrom}, but all values are nullable */ -export type NullableFormControlsFrom = { [K in keyof Required]: FormControl }; +export type NullableFormControlsFrom = { + [K in keyof Required]: FormControl; +}; diff --git a/libs/ng/src/lib/mat-list/components/list-sort-icon/list-sort-icon.component.html b/libs/ng/src/lib/mat-list/components/list-sort-icon/list-sort-icon.component.html index e189d1a1..6c516082 100644 --- a/libs/ng/src/lib/mat-list/components/list-sort-icon/list-sort-icon.component.html +++ b/libs/ng/src/lib/mat-list/components/list-sort-icon/list-sort-icon.component.html @@ -1,3 +1,6 @@ -{{ - isDesc ? "arrow_downward" : "arrow_upward" -}} +{{ isDesc ? "arrow_downward" : "arrow_upward" }} diff --git a/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.spec.ts b/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.spec.ts index 9ab705fc..d9e526ea 100644 --- a/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.spec.ts +++ b/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.spec.ts @@ -7,19 +7,29 @@ import { ListSortOrderValueDefault } from "../../list-sort.columns"; describe("ListTableHeaderComponent", () => { describe("Static function", () => { it("should get the next default direction", () => { - const nextDirection = ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); - - expect(nextDirection(false)).toBe("asc" satisfies ListSortOrderValueDefault); - expect(nextDirection("asc")).toBe("desc" satisfies ListSortOrderValueDefault); - expect(nextDirection("desc")).toBeFalse(); + const nextDirection = + ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(); + + expect(nextDirection(false)).toBe( + "asc" satisfies ListSortOrderValueDefault + ); + expect(nextDirection("asc")).toBe( + "desc" satisfies ListSortOrderValueDefault + ); + expect(nextDirection("desc")).toBe(false); }); it("should get the next default direction (reversed)", () => { - const nextDirection = ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(true); - - expect(nextDirection(false)).toBe("desc" satisfies ListSortOrderValueDefault); - expect(nextDirection("desc")).toBe("asc" satisfies ListSortOrderValueDefault); - expect(nextDirection("asc")).toBeFalse(); + const nextDirection = + ListTableHeaderComponent.DEFAULT_NEXT_DIRECTION(true); + + expect(nextDirection(false)).toBe( + "desc" satisfies ListSortOrderValueDefault + ); + expect(nextDirection("desc")).toBe( + "asc" satisfies ListSortOrderValueDefault + ); + expect(nextDirection("asc")).toBe(false); }); }); diff --git a/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.ts b/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.ts index bf896239..37d93b23 100644 --- a/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.ts +++ b/libs/ng/src/lib/mat-list/components/list-table-header/list-table-header.component.ts @@ -1,8 +1,18 @@ import { CommonModule } from "@angular/common"; -import { Component, ContentChild, ElementRef, EventEmitter, Input, Output } from "@angular/core"; +import { + Component, + ContentChild, + ElementRef, + EventEmitter, + Input, + Output +} from "@angular/core"; import { OrderValue } from "~/lib/common/endpoints"; -import { ListSortColumns, ListSortOrderValueDefault } from "../../list-sort.columns"; +import { + ListSortColumns, + ListSortOrderValueDefault +} from "../../list-sort.columns"; import { ListSortIconComponent } from "../list-sort-icon/list-sort-icon.component"; @Component({ @@ -13,7 +23,10 @@ import { ListSortIconComponent } from "../list-sort-icon/list-sort-icon.componen imports: [CommonModule, ListSortIconComponent] }) -export class ListTableHeaderComponent { +export class ListTableHeaderComponent< + COLUMN extends number | string, + ORDER extends OrderValue +> { /** * Creates a {@link ListSortColumns}'s update * diff --git a/libs/ng/src/lib/mat-list/list-sort.columns.ts b/libs/ng/src/lib/mat-list/list-sort.columns.ts index 31476777..e2efe5ca 100644 --- a/libs/ng/src/lib/mat-list/list-sort.columns.ts +++ b/libs/ng/src/lib/mat-list/list-sort.columns.ts @@ -3,7 +3,10 @@ import { OrderValue } from "~/lib/common/endpoints"; /** * A single column */ -export interface ListSortColumn { +export interface ListSortColumn< + T extends number | string, + U extends OrderValue = OrderValue +> { /** * The column identifier */ @@ -31,7 +34,9 @@ export class ListSortColumns< * * @param columns the initial columns */ - public constructor(public readonly columns: ReadonlyArray> = []) {} + public constructor( + public readonly columns: ReadonlyArray> = [] + ) {} /** * Finds an item from the columns diff --git a/libs/ng/src/lib/request-state/cards/net-error/http-error.card.html b/libs/ng/src/lib/request-state/cards/net-error/http-error.card.html index fcf78044..bbebcc19 100644 --- a/libs/ng/src/lib/request-state/cards/net-error/http-error.card.html +++ b/libs/ng/src/lib/request-state/cards/net-error/http-error.card.html @@ -2,7 +2,9 @@ - cards.http-error.title + cards.http-error.title {{ error.status | translateHttpError }} @@ -15,17 +17,23 @@ > front_hand -

cards.http-error.errors.400.description

+

+ cards.http-error.errors.400.description +

visibility_off -

cards.http-error.errors.404.description

+

+ cards.http-error.errors.404.description +

storage -

cards.http-error.errors.500.description

+

+ cards.http-error.errors.500.description +

diff --git a/libs/ng/src/lib/request-state/cards/net-error/http-error.card.ts b/libs/ng/src/lib/request-state/cards/net-error/http-error.card.ts index a79ef3fe..d4bb9151 100644 --- a/libs/ng/src/lib/request-state/cards/net-error/http-error.card.ts +++ b/libs/ng/src/lib/request-state/cards/net-error/http-error.card.ts @@ -19,7 +19,13 @@ import { TranslationModule } from "../../../translation"; styleUrls: ["./http-error.card.scss"], templateUrl: "./http-error.card.html", - imports: [CommonModule, MatCardModule, TranslateModule, MatIconModule, TranslationModule] + imports: [ + CommonModule, + MatCardModule, + TranslateModule, + MatIconModule, + TranslationModule + ] }) export class HttpErrorCard { /** diff --git a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.html b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.html index dad4a7dc..e5553df0 100644 --- a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.html +++ b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.html @@ -1,5 +1,9 @@
- + diff --git a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.spec.ts b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.spec.ts index 7db23eaa..de650729 100644 --- a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.spec.ts +++ b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.spec.ts @@ -11,7 +11,9 @@ describe("RequestStateWrapperComponent", () => { imports: [RequestStateWrapperComponent] }).compileComponents(); - fixture = TestBed.createComponent(RequestStateWrapperComponent) as never; + fixture = TestBed.createComponent( + RequestStateWrapperComponent + ) as never; component = fixture.componentInstance; component.requestState = { state: "init" }; fixture.detectChanges(); diff --git a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.stories.ts b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.stories.ts index 683c4280..6d948a93 100644 --- a/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.stories.ts +++ b/libs/ng/src/lib/request-state/components/request-state-wrapper/request-state-wrapper.component.stories.ts @@ -17,7 +17,10 @@ type RState = RequestState; const stateInit: RState = { state: "init" }; const stateLoading: RState = { error: false, state: "loading" }; -const stateFailed: RState = { error: new HttpErrorResponse({ status: 404 }), state: "failed" }; +const stateFailed: RState = { + error: new HttpErrorResponse({ status: 404 }), + state: "failed" +}; export const Primary: Story = { args: { requestState: stateLoading } @@ -42,7 +45,10 @@ const getOnUseTemplate = (template: string, action?: string) => ` `; export const OnUseLoading: StoryFn = args => ({ - props: { requestState: stateLoading, ...args } satisfies Partial, + props: { + requestState: stateLoading, + ...args + } satisfies Partial, template: getOnUseTemplate(`

Some skeleton, or content

`) }); diff --git a/libs/ng/src/lib/request-state/request-state.snapshot.spec.ts b/libs/ng/src/lib/request-state/request-state.snapshot.spec.ts index 40edfa3c..37a9706b 100644 --- a/libs/ng/src/lib/request-state/request-state.snapshot.spec.ts +++ b/libs/ng/src/lib/request-state/request-state.snapshot.spec.ts @@ -1,5 +1,8 @@ import { RequestState } from "./request-state"; -import { getRequestStateSnapshot, RequestStateSnapshot } from "./request-state.snapshot"; +import { + getRequestStateSnapshot, + RequestStateSnapshot +} from "./request-state.snapshot"; describe("RequestStateSnapshot", () => { describe("getRequestStateSnapshot", () => { @@ -10,20 +13,29 @@ describe("RequestStateSnapshot", () => { const stateInit = { state: "init" } as const satisfies State; - const stateLoading = { error: false, state: "loading" } as const satisfies State; + const stateLoading = { + error: false, + state: "loading" + } as const satisfies State; const stateLoadingWithData = { data: "ok", error: false, state: "loading" } as const satisfies State; - const stateLoadingWithError = { error: 1, state: "loading" } as const satisfies State; + const stateLoadingWithError = { + error: 1, + state: "loading" + } as const satisfies State; const stateLoadingWithDataAndError = { data: "was ok", error: 2, state: "loading" } as const satisfies State; - const stateFailed = { error: 1, state: "failed" } as const satisfies State; + const stateFailed = { + error: 1, + state: "failed" + } as const satisfies State; const stateFailedWithData = { data: "was ok", error: 3, @@ -40,8 +52,14 @@ describe("RequestStateSnapshot", () => { for (const [state, snapshot] of [ [stateInit, { isLoading: false }], [stateLoading, { isLoading: true }], - [stateLoadingWithData, { data: stateLoadingWithData.data, isLoading: true }], - [stateLoadingWithError, { error: stateLoadingWithError.error, isLoading: true }], + [ + stateLoadingWithData, + { data: stateLoadingWithData.data, isLoading: true } + ], + [ + stateLoadingWithError, + { error: stateLoadingWithError.error, isLoading: true } + ], [ stateLoadingWithDataAndError, { @@ -61,7 +79,9 @@ describe("RequestStateSnapshot", () => { ], [stateSuccess, { data: stateSuccess.data, isLoading: false }] ] satisfies Array<[State, Snapshot]>) { - expect(getRequestStateSnapshot(state)).toStrictEqual(snapshot); + expect( + getRequestStateSnapshot(state) + ).toStrictEqual(snapshot); } }); }); diff --git a/libs/ng/src/lib/request-state/request-state.snapshot.ts b/libs/ng/src/lib/request-state/request-state.snapshot.ts index ff50f8a2..1575d1b2 100644 --- a/libs/ng/src/lib/request-state/request-state.snapshot.ts +++ b/libs/ng/src/lib/request-state/request-state.snapshot.ts @@ -48,7 +48,9 @@ export function getRequestStateSnapshot( ...pick(state, ["data"]), isLoading: true }; - return state.error === false ? snapshot : { ...snapshot, error: state.error }; + return state.error === false + ? snapshot + : { ...snapshot, error: state.error }; } case "success": return { data: state.data, isLoading: false }; diff --git a/libs/ng/src/lib/request-state/request-state.subject.spec.ts b/libs/ng/src/lib/request-state/request-state.subject.spec.ts index c31b0870..18d04a7c 100644 --- a/libs/ng/src/lib/request-state/request-state.subject.spec.ts +++ b/libs/ng/src/lib/request-state/request-state.subject.spec.ts @@ -39,24 +39,46 @@ describe("RequestStateSubject", () => { return sem .tryReadOne(timeout) .then(async stateInit => { - expect(stateInit.state).toBe("init" satisfies RequestStateState); + expect(stateInit.state).toBe( + "init" satisfies RequestStateState + ); const value1 = 200; void requestState$.request(timeout, value1); - const [stateLoading1, stateSuccess1] = await sem.tryRead(timeoutMax, 2); - expect(stateLoading1.state).toBe("loading" satisfies RequestStateState); - expect(stateSuccess1.state).toBe("success" satisfies RequestStateState); - expect((stateSuccess1 as RequestSucceed).data).toBe(value1); + const [stateLoading1, stateSuccess1] = await sem.tryRead( + timeoutMax, + 2 + ); + expect(stateLoading1.state).toBe( + "loading" satisfies RequestStateState + ); + expect(stateSuccess1.state).toBe( + "success" satisfies RequestStateState + ); + expect((stateSuccess1 as RequestSucceed).data).toBe( + value1 + ); const value2 = 400; void requestState$.request(timeout, value2); - const [stateLoading2, stateSuccess2] = await sem.tryRead(timeoutMax, 2); - expect(stateLoading2.state).toBe("loading" satisfies RequestStateState); - expect((stateLoading2 as RequestLoading).data).toBe(value1); - expect(stateSuccess2.state).toBe("success" satisfies RequestStateState); - expect((stateSuccess2 as RequestSucceed).data).toBe(value2); + const [stateLoading2, stateSuccess2] = await sem.tryRead( + timeoutMax, + 2 + ); + expect(stateLoading2.state).toBe( + "loading" satisfies RequestStateState + ); + expect( + (stateLoading2 as RequestLoading).data + ).toBe(value1); + expect(stateSuccess2.state).toBe( + "success" satisfies RequestStateState + ); + expect((stateSuccess2 as RequestSucceed).data).toBe( + value2 + ); }) .finally(() => subscription.unsubscribe()); }); @@ -71,15 +93,26 @@ describe("RequestStateSubject", () => { return sem .tryReadOne(timeout) .then(async stateInit => { - expect(stateInit.state).toBe("init" satisfies RequestStateState); + expect(stateInit.state).toBe( + "init" satisfies RequestStateState + ); const value = -200; void requestState$.request(timeout, value).catch(() => void 0); - const [stateLoading, stateFailed] = await sem.tryRead(timeoutMax, 2); - expect(stateLoading.state).toBe("loading" satisfies RequestStateState); - expect(stateFailed.state).toBe("failed" satisfies RequestStateState); - expect((stateFailed as RequestFailed).error).toBe(value); + const [stateLoading, stateFailed] = await sem.tryRead( + timeoutMax, + 2 + ); + expect(stateLoading.state).toBe( + "loading" satisfies RequestStateState + ); + expect(stateFailed.state).toBe( + "failed" satisfies RequestStateState + ); + expect( + (stateFailed as RequestFailed).error + ).toBe(value); }) .finally(() => subscription.unsubscribe()); }); diff --git a/libs/ng/src/lib/request-state/request-state.subject.ts b/libs/ng/src/lib/request-state/request-state.subject.ts index 329150ff..d16e1475 100644 --- a/libs/ng/src/lib/request-state/request-state.subject.ts +++ b/libs/ng/src/lib/request-state/request-state.subject.ts @@ -2,18 +2,28 @@ import { HttpErrorResponse } from "@angular/common/http"; import { BehaviorSubject, Observable } from "rxjs"; import { RequestState } from "./request-state"; -import { getRequestStateSnapshot, RequestStateWithSnapshot } from "./request-state.snapshot"; +import { + getRequestStateSnapshot, + RequestStateWithSnapshot +} from "./request-state.snapshot"; /** * An observable of the state of any async request. * * It splits the error into a field to be more easily used in template */ -export class RequestStateSubject +export class RequestStateSubject< + T, + E = HttpErrorResponse, + ARGS extends readonly unknown[] = never + > extends Observable> - implements Pick>, "getValue"> + implements + Pick>, "getValue"> { - private readonly subject = new BehaviorSubject>( + private readonly subject = new BehaviorSubject< + RequestStateWithSnapshot + >( this.getRequestWithSnapshot({ state: "init" }) @@ -59,7 +69,9 @@ export class RequestStateSubject): RequestStateWithSnapshot { + private getRequestWithSnapshot( + state: RequestState + ): RequestStateWithSnapshot { return { ...state, snapshot: getRequestStateSnapshot(state) }; } private next(state: RequestState) { diff --git a/libs/ng/src/lib/rete/rete.connection.ts b/libs/ng/src/lib/rete/rete.connection.ts index 97afefbf..044ea028 100644 --- a/libs/ng/src/lib/rete/rete.connection.ts +++ b/libs/ng/src/lib/rete/rete.connection.ts @@ -13,7 +13,12 @@ export class ReteConnection extends ClassicPreset.Connection< output: ReteOutput, input: ReteInput ) { - super(output.node, output.output._id.toString(), input.node, input.input._id.toString()); + super( + output.node, + output.output._id.toString(), + input.node, + input.input._id.toString() + ); } // TODO? diff --git a/libs/ng/src/lib/translation/translation.module.ts b/libs/ng/src/lib/translation/translation.module.ts index c0193a1b..efcf67d8 100644 --- a/libs/ng/src/lib/translation/translation.module.ts +++ b/libs/ng/src/lib/translation/translation.module.ts @@ -22,12 +22,18 @@ export interface TranslationModuleConfig { @NgModule({ declarations: [TranslationControlErrorPipe, TranslationHttpErrorPipe], - exports: [TranslateModule, TranslationControlErrorPipe, TranslationHttpErrorPipe], + exports: [ + TranslateModule, + TranslationControlErrorPipe, + TranslationHttpErrorPipe + ], imports: [TranslateModule], providers: [TranslationService] }) export class TranslationModule { - public static forRoot(config: TranslationModuleConfig): ModuleWithProviders { + public static forRoot( + config: TranslationModuleConfig + ): ModuleWithProviders { const module = TranslateModule.forRoot({ defaultLanguage: "en" satisfies TranslationLanguage, isolate: false, diff --git a/libs/ng/src/lib/translation/translation.service.spec.ts b/libs/ng/src/lib/translation/translation.service.spec.ts index e238d7a7..67547b88 100644 --- a/libs/ng/src/lib/translation/translation.service.spec.ts +++ b/libs/ng/src/lib/translation/translation.service.spec.ts @@ -7,7 +7,9 @@ describe("TranslationService", () => { let service: TranslationService; beforeEach(() => { - TestBed.configureTestingModule({ imports: [TranslateTestingModule.withTranslations({})] }); + TestBed.configureTestingModule({ + imports: [TranslateTestingModule.withTranslations({})] + }); service = TestBed.inject(TranslationService); }); diff --git a/libs/ng/src/lib/translation/translation.service.ts b/libs/ng/src/lib/translation/translation.service.ts index 5965abfb..08f55562 100644 --- a/libs/ng/src/lib/translation/translation.service.ts +++ b/libs/ng/src/lib/translation/translation.service.ts @@ -23,15 +23,21 @@ export class TranslationService { * @param errors the errors received * @returns the error message for an abstract control errors, empty string if no error */ - public translateControlError(errors: ValidationErrors | null): Observable { + public translateControlError( + errors: ValidationErrors | null + ): Observable { if (errors === null) { return of(""); } if (errors["required"] !== undefined) { - return this.translate.stream("errors.validation.required") as Observable; + return this.translate.stream( + "errors.validation.required" + ) as Observable; } if (errors["email"] !== undefined) { - return this.translate.stream("errors.validation.email") as Observable; + return this.translate.stream( + "errors.validation.email" + ) as Observable; } if (errors["minlength"] !== undefined) { return this.translate.stream( @@ -40,7 +46,9 @@ export class TranslationService { ) as Observable; } - return this.translate.stream("errors.validation.invalid") as Observable; + return this.translate.stream( + "errors.validation.invalid" + ) as Observable; } /** @@ -52,17 +60,29 @@ export class TranslationService { public translateHttpError(status: number): Observable { switch (status) { case 400: - return this.translate.stream("errors.http.400") as Observable; + return this.translate.stream( + "errors.http.400" + ) as Observable; case 401: - return this.translate.stream("errors.http.401") as Observable; + return this.translate.stream( + "errors.http.401" + ) as Observable; case 403: - return this.translate.stream("errors.http.403") as Observable; + return this.translate.stream( + "errors.http.403" + ) as Observable; case 404: - return this.translate.stream("errors.http.404") as Observable; + return this.translate.stream( + "errors.http.404" + ) as Observable; case 500: - return this.translate.stream("errors.http.500") as Observable; + return this.translate.stream( + "errors.http.500" + ) as Observable; } - return this.translate.stream("errors.http.generic") as Observable; + return this.translate.stream( + "errors.http.generic" + ) as Observable; } } diff --git a/libs/ng/test/setup.ts b/libs/ng/test/setup.ts index 8b195858..c9dfe631 100644 --- a/libs/ng/test/setup.ts +++ b/libs/ng/test/setup.ts @@ -8,6 +8,10 @@ import { ApiTestingModule } from "../src/lib/api/testing/api-testing.module"; TestBed.configureTestingModule({ errorOnUnknownElements: true, errorOnUnknownProperties: true, - imports: [ApiTestingModule, TranslateTestingModule.withTranslations({}), NoopAnimationsModule], + imports: [ + ApiTestingModule, + TranslateTestingModule.withTranslations({}), + NoopAnimationsModule + ], teardown: { destroyAfterEach: true } }); diff --git a/libs/ng/tsconfig.lib.json b/libs/ng/tsconfig.lib.json index 7873e800..d25c1dcf 100644 --- a/libs/ng/tsconfig.lib.json +++ b/libs/ng/tsconfig.lib.json @@ -6,7 +6,12 @@ "outDir": "../../dist/out-tsc", "types": [] }, - "exclude": ["test/setup.ts", "src/**/*.spec.ts", "jest.config.ts", "src/**/*.test.ts"], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "test/setup.ts" + ], "extends": "./tsconfig.json", "include": ["src/**/*.ts"] } diff --git a/libs/ng/tsconfig.spec.json b/libs/ng/tsconfig.spec.json index ffd98040..d7f7df45 100644 --- a/libs/ng/tsconfig.spec.json +++ b/libs/ng/tsconfig.spec.json @@ -6,5 +6,10 @@ }, "extends": "./tsconfig.json", "files": ["test/setup.ts"], - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "jest.config.ts", + "src/**/*.d.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] } diff --git a/nx.json b/nx.json index be15132f..2b71b9cd 100644 --- a/nx.json +++ b/nx.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/nx/schemas/nx-schema.json", + "affected": { + "defaultBase": "master" + }, "defaultBase": "master", "generators": { "@nx/angular:application": { @@ -17,18 +20,18 @@ } }, "namedInputs": { - "default": ["{projectRoot}/**/*", "sharedGlobals"], + "default": ["sharedGlobals", "{projectRoot}/**/*"], "production": [ - "default", + "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", - "!{projectRoot}/tsconfig.spec.json", - "!{projectRoot}/jest.config.[jt]s", "!{projectRoot}/.eslintrc.json", "!{projectRoot}/.storybook/**/*", - "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)", "!{projectRoot}/.stylelintrc(.(json|yml|yaml|js))?", + "!{projectRoot}/jest.config.[jt]s", "!{projectRoot}/src/test-setup.[jt]s", - "!{projectRoot}/tsconfig.storybook.json" + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/tsconfig.storybook.json", + "default" ], "sharedGlobals": [] }, @@ -36,10 +39,13 @@ "targetDefaults": { "build": { "dependsOn": ["^build"], - "inputs": ["production", "^production"] + "inputs": ["^production", "production"] }, "e2e": { - "inputs": ["default", "^production"] + "inputs": ["^production", "default"], + "options": { + "runInBand": true + } }, "lint": { "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], @@ -49,17 +55,30 @@ }, "storybook-build": { "inputs": [ - "default", - "^production", "!{projectRoot}/.storybook/**/*", + "^production", + "default", "{projectRoot}/tsconfig.storybook.json" ] }, "stylelint": { - "inputs": ["default", "{workspaceRoot}/.stylelintrc(.(json|yml|yaml|js))?"] + "inputs": [ + "default", + "{workspaceRoot}/.stylelintrc(.(json|yml|yaml|js))?" + ] }, "test": { - "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + }, + "inputs": [ + "^production", + "default", + "{workspaceRoot}/jest.preset.js" + ] } }, "tasksRunnerOptions": { diff --git a/package-lock.json b/package-lock.json index ddcf9440..278f871a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,6 @@ "@nx/webpack": "16.10.0", "@nx/workspace": "16.10.0", "@release-it/conventional-changelog": "^7.0.0", - "@shopify/eslint-plugin": "^43.0.0", "@storybook/addon-actions": "^7.3.2", "@storybook/addon-essentials": "^7.4.0", "@storybook/addon-interactions": "^7.2.1", @@ -130,7 +129,6 @@ "eslint-plugin-etc": "^2.0.2", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jest": "^27.2.3", - "eslint-plugin-jest-extended": "^2.0.0", "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-jsonc": "^2.6.0", "eslint-plugin-markdownlint": "^0.5.0", @@ -1853,49 +1851,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/eslint-parser": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.11.tgz", - "integrity": "sha512-YjOYZ3j7TjV8OhLW6NCtyg8G04uStATEUe5eiLuCZaXz2VSDQ3dsAtm2D+TuQyAqNMUK2WacGo0/uma9Pein1w==", - "dev": true, - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/eslint-parser/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/eslint-plugin": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.22.10.tgz", - "integrity": "sha512-SRZcvo3fnO5h79B9DZSV6LG2vHH7OWsSNp1huFLHsXKyytRG413byQk9zxW1VcPOhnzfx2VIUz+8aGbiE7fOkA==", - "dev": true, - "dependencies": { - "eslint-rule-composer": "^0.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/eslint-parser": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, "node_modules/@babel/generator": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", @@ -9045,37 +9000,6 @@ "rxjs": "^6.5.5 || ^7.4.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -12121,241 +12045,6 @@ } } }, - "node_modules/@shopify/eslint-plugin": { - "version": "43.0.0", - "resolved": "https://registry.npmjs.org/@shopify/eslint-plugin/-/eslint-plugin-43.0.0.tgz", - "integrity": "sha512-+/bLrmODh+OtPzf4YafsfpAbcdZfHOHBQGXgzoXPge6yUuyOo+NJiNvq0LOwo6jaRkaBgWQTUZB8HRlx4swHag==", - "dev": true, - "dependencies": { - "@babel/eslint-parser": "^7.16.3", - "@babel/eslint-plugin": "^7.14.5", - "@typescript-eslint/eslint-plugin": "^6.2.1", - "@typescript-eslint/parser": "^6.2.1", - "change-case": "^4.1.2", - "common-tags": "^1.8.2", - "doctrine": "^2.1.0", - "eslint-config-prettier": "^8.10.0", - "eslint-module-utils": "^2.7.1", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.28.0", - "eslint-plugin-jest": "^27.2.3", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-sort-class-members": "^1.18.0", - "jsx-ast-utils": "^3.2.1", - "pkg-dir": "^5.0.0", - "pluralize": "^8.0.0" - }, - "peerDependencies": { - "eslint": "^8.3.0" - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.4", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@shopify/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -18888,19 +18577,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" - } - }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", @@ -19046,12 +18722,6 @@ "node": ">= 0.6" } }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true - }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -19091,15 +18761,6 @@ "node": ">= 4" } }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -19174,15 +18835,6 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, - "node_modules/axe-core": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", - "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/axios": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", @@ -19206,15 +18858,6 @@ "node": ">= 6" } }, - "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -20513,17 +20156,6 @@ } ] }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -20553,26 +20185,6 @@ "node": ">=4" } }, - "node_modules/change-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "capital-case": "^1.0.4", - "constant-case": "^3.0.4", - "dot-case": "^3.0.4", - "header-case": "^2.0.4", - "no-case": "^3.0.4", - "param-case": "^3.0.4", - "pascal-case": "^3.1.2", - "path-case": "^3.0.4", - "sentence-case": "^3.0.4", - "snake-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -21550,17 +21162,6 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, - "node_modules/constant-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case": "^2.0.2" - } - }, "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -23113,12 +22714,6 @@ "type": "^1.0.1" } }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -24460,28 +24055,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-iterator-helpers": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.14.tgz", - "integrity": "sha512-JgtVnwiuoRuzLvqelrvN3Xu7H9bu2ap/kQ2CrM62iidP8SKuD99rWU3CJy++s7IVL2qb/AjXPGR/E7i9ngd/Cw==", - "dev": true, - "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "iterator.prototype": "^1.1.0", - "safe-array-concat": "^1.0.0" - } - }, "node_modules/es-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", @@ -25298,25 +24871,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, "node_modules/eslint-plugin-eslint-comments": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", @@ -25473,33 +25027,6 @@ } } }, - "node_modules/eslint-plugin-jest-extended": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest-extended/-/eslint-plugin-jest-extended-2.0.0.tgz", - "integrity": "sha512-nMhVVsVcG/+Q6FMshql35WBxwx8xlBhxKgAG08WP3BYWfXrp28oxLpJVu9JSbMpfmfKGVrHwMYJGfPLRKlGB8w==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "^5.10.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-jest-formatting": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest-formatting/-/eslint-plugin-jest-formatting-3.1.0.tgz", - "integrity": "sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=0.8.0" - } - }, "node_modules/eslint-plugin-jsdoc": { "version": "46.10.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", @@ -25568,57 +25095,6 @@ "eslint": ">=6.0.0" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-markdownlint": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-markdownlint/-/eslint-plugin-markdownlint-0.5.0.tgz", @@ -25634,35 +25110,6 @@ "eslint": ">=7.5.0" } }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -25693,98 +25140,6 @@ } } }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-sonarjs": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.23.0.tgz", @@ -25806,18 +25161,6 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-sort-class-members": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.18.0.tgz", - "integrity": "sha512-y4r5OC3LJNHJZCWfVwFnnRiNrQ/LRf7Pb1wD6/CP8Y4qmUvjtmkwrLvyY755p8SFTOOXVd33HgFuF3XxVW1xbg==", - "dev": true, - "engines": { - "node": ">=4.0.0" - }, - "peerDependencies": { - "eslint": ">=0.8.0" - } - }, "node_modules/eslint-plugin-sort-decorators": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/eslint-plugin-sort-decorators/-/eslint-plugin-sort-decorators-0.2.5.tgz", @@ -26035,39 +25378,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -28611,16 +27921,6 @@ "he": "bin/he" } }, - "node_modules/header-case": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", - "dev": true, - "dependencies": { - "capital-case": "^1.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -29564,21 +28864,6 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -29713,18 +28998,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -30425,18 +29698,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/iterator.prototype": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.1.tgz", - "integrity": "sha512-9E+nePc8C9cnQldmNl6bgpTY6zI4OPRZd97fhJ/iVZ1GifIUDVV5F6x1nEDqpe8KaMEZGT4xgrwKQDxXnjOIZQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.0", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.3" - } - }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -32620,21 +31881,6 @@ "verror": "1.10.0" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -32766,21 +32012,6 @@ "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", "dev": true }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dev": true, - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, "node_modules/latest-version": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", @@ -35628,20 +34859,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object.fromentries": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", @@ -35671,19 +34888,6 @@ "get-intrinsic": "^1.2.1" } }, - "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", @@ -36404,16 +35608,6 @@ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, - "node_modules/path-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -37643,23 +36837,6 @@ "node": ">= 6" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/propagating-hammerjs": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.5.0.tgz", @@ -38738,26 +37915,6 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -38822,18 +37979,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -40572,17 +39717,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/sentence-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, "node_modules/serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -40898,16 +40032,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -41718,25 +40842,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz", - "integrity": "sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", @@ -43269,18 +42374,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -44210,24 +43303,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/upper-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -45152,32 +44227,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", - "dev": true, - "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/which-collection": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", diff --git a/package.json b/package.json index f9720964..569353ed 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "@nx/webpack": "16.10.0", "@nx/workspace": "16.10.0", "@release-it/conventional-changelog": "^7.0.0", - "@shopify/eslint-plugin": "^43.0.0", "@storybook/addon-actions": "^7.3.2", "@storybook/addon-essentials": "^7.4.0", "@storybook/addon-interactions": "^7.2.1", @@ -128,7 +127,6 @@ "eslint-plugin-etc": "^2.0.2", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jest": "^27.2.3", - "eslint-plugin-jest-extended": "^2.0.0", "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-jsonc": "^2.6.0", "eslint-plugin-markdownlint": "^0.5.0", @@ -204,7 +202,7 @@ "build": "nx run-many --target=build --parallel=3", "compodoc": "nx run-many --target=compodoc --output-style=stream", "compodoc:build": "nx run-many --target=compodoc --configuration build", - "compodoc:coverage": "nx run-many --target=compodoc --configuration coverage", + "compodoc:coverage": "nx affected -t compodoc --configuration coverage", "frontend:build": "nx build frontend", "frontend:compodoc": "nx run frontend:compodoc", "frontend:compodoc:build": "nx run frontend:compodoc:build", @@ -240,7 +238,7 @@ "libs:ng:test": "nx run ng:test", "libs:ng:test:watch": "npm run libs:ng:test -- --watch", "lint": "npm run lint:code; C1=$?; npm run lint:style; C2=$?; npm run lint:other && [ $C1 -eq 0 ] && [ $C2 -eq 0 ]", - "lint:code": "nx run-many --all --target=lint", + "lint:code": "nx affected -t lint", "lint:code:fix": "npm run lint:code -- --fix", "lint:fix": "npm run lint:code:fix; npm run lint:style:fix; npm run lint:other:fix", "lint:other": "npx eslint . --ignore-pattern apps/ --ignore-pattern libs/", @@ -248,9 +246,9 @@ "lint:style": "npm run frontend:lint:style", "lint:style:fix": "npm run frontend:lint:style:fix", "postinstall": "ts-node tools/npm/postinstall.ts", - "test": "nx run-many --target=test --parallel=4 --runInBand --coverage", + "test": "nx affected -t test --parallel=4", "test:ci": "nx run-many --target=test --configuration ci --ci --parallel=4 --runInBand", - "test:e2e": "nx run-many --target=e2e --parallel=false --runInBand" + "test:e2e": "nx affected -t e2e --parallel=false" }, "version": "0.6.4" } diff --git a/renovate.json b/renovate.json index d79cd4d1..3595bfab 100644 --- a/renovate.json +++ b/renovate.json @@ -11,13 +11,17 @@ "matchPackageNames": ["cypress"] }, { - "extends": ["packages:eslint", "packages:stylelint"], + "groupName": "Passport", + "matchPackageNames": ["@nestjs/passport"], + "matchPackagePrefixes": ["passport"] + }, + { + "extends": ["packages:eslint"], "groupName": "Linting packages", "groupSlug": "linting", "matchPackageNames": [ "@cspell/eslint-plugin", - "@darraghor/eslint-plugin-nestjs-typed", - "@shopify/eslint-plugin" + "@darraghor/eslint-plugin-nestjs-typed" ] }, { diff --git a/tools/jest/jest-extended.ts b/tools/jest/jest-extended.ts deleted file mode 100644 index ee87aeea..00000000 --- a/tools/jest/jest-extended.ts +++ /dev/null @@ -1,6 +0,0 @@ -// https://jest-extended.jestcommunity.dev/docs/getting-started/setup - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-var-requires -- FIXME -const matchers: jest.ExpectExtendMap = require("jest-extended"); - -(expect as unknown as jest.Expect).extend(matchers); diff --git a/tools/npm/postinstall.ts b/tools/npm/postinstall.ts index fb7777f7..08ca9fb0 100644 --- a/tools/npm/postinstall.ts +++ b/tools/npm/postinstall.ts @@ -13,7 +13,10 @@ function bootstrap() { // Copy the config template for the backend const configPath = path.join(pathBack, "src/config.ts"); if (!fs.existsSync(configPath)) { - const configTemplate = path.join(pathBack, "src/configuration/config.template"); + const configTemplate = path.join( + pathBack, + "src/configuration/config.template" + ); fs.copyFileSync(configTemplate, configPath); } diff --git a/tsconfig.base.json b/tsconfig.base.json index 85e2ab4b..a97719d1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, - "lib": ["es2022", "dom"], + "lib": ["dom", "es2022"], "module": "esnext", "moduleResolution": "node", "noFallthroughCasesInSwitch": true,