diff --git a/.vscode/launch.json b/.vscode/launch.json index 95d7903cd6..0d6ebd10b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -656,6 +656,22 @@ "cwd": "${workspaceRoot}/dev/doc-import-tool", "protocol": "inspector" }, + { + "name": "Debug process", + "type": "node", + "request": "launch", + "args": ["src/index.ts"], + "env": { + "SECRET": "secret", + "ACCOUNTS_URL": "http://localhost:3000", + "QUEUE_CONFIG": "localhost:19092", + "QUEUE_REGION": "cockroach", + }, + "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], + "runtimeVersion": "20", + "sourceMaps": true, + "cwd": "${workspaceRoot}/services/process" + }, { "name": "Debug print", "type": "node", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index e4fcbb4eae..35bd13c2ef 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -916,6 +916,9 @@ importers: '@rush-temp/pod-print': specifier: file:./projects/pod-print.tgz version: file:projects/pod-print.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@rush-temp/pod-process': + specifier: file:./projects/pod-process.tgz + version: file:projects/pod-process.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9)) '@rush-temp/pod-server': specifier: file:./projects/pod-server.tgz version: file:projects/pod-server.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9)) @@ -1447,9 +1450,6 @@ importers: '@rush-temp/workspace-service': specifier: file:./projects/workspace-service.tgz version: file:projects/workspace-service.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9)) - '@sentry/node': - specifier: ^9.22.0 - version: 9.22.0 '@sentry/svelte': specifier: ^9.22.0 version: 9.22.0(svelte@4.2.19) @@ -3747,10 +3747,6 @@ packages: resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} engines: {node: '>=8.0.0'} - '@opentelemetry/api-logs@0.57.2': - resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} - engines: {node: '>=14'} - '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -3762,24 +3758,12 @@ packages: '@opentelemetry/api': ^1.4.1 '@opentelemetry/core': ^2.0.0 - '@opentelemetry/context-async-hooks@1.30.1': - resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/context-async-hooks@2.0.1': resolution: {integrity: sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.30.1': - resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.0.1': resolution: {integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3858,12 +3842,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 - '@opentelemetry/instrumentation-amqplib@0.46.1': - resolution: {integrity: sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-amqplib@0.50.0': resolution: {integrity: sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3894,12 +3872,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.43.1': - resolution: {integrity: sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-connect@0.47.0': resolution: {integrity: sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3912,12 +3884,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 - '@opentelemetry/instrumentation-dataloader@0.16.1': - resolution: {integrity: sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-dataloader@0.21.0': resolution: {integrity: sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3930,12 +3896,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.47.1': - resolution: {integrity: sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.52.0': resolution: {integrity: sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3948,36 +3908,18 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.19.1': - resolution: {integrity: sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fs@0.23.0': resolution: {integrity: sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-generic-pool@0.43.1': - resolution: {integrity: sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-generic-pool@0.47.0': resolution: {integrity: sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.47.1': - resolution: {integrity: sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.51.0': resolution: {integrity: sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3990,12 +3932,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.45.2': - resolution: {integrity: sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.50.0': resolution: {integrity: sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4008,18 +3944,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.57.2': - resolution: {integrity: sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-ioredis@0.47.1': - resolution: {integrity: sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.51.0': resolution: {integrity: sha512-9IUws0XWCb80NovS+17eONXsw1ZJbHwYYMXiwsfR9TSurkLV5UNbRSKb9URHO+K+pIJILy9wCxvyiOneMr91Ig==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4032,42 +3956,18 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-kafkajs@0.7.1': - resolution: {integrity: sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation-knex@0.44.1': - resolution: {integrity: sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-knex@0.48.0': resolution: {integrity: sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.47.1': - resolution: {integrity: sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.51.0': resolution: {integrity: sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-lru-memoizer@0.44.1': - resolution: {integrity: sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-lru-memoizer@0.48.0': resolution: {integrity: sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4080,48 +3980,24 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.52.0': - resolution: {integrity: sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.56.0': resolution: {integrity: sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.46.1': - resolution: {integrity: sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.50.0': resolution: {integrity: sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.45.2': - resolution: {integrity: sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.49.0': resolution: {integrity: sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.45.1': - resolution: {integrity: sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.49.0': resolution: {integrity: sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4146,12 +4022,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.51.1': - resolution: {integrity: sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.55.0': resolution: {integrity: sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4164,12 +4034,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis-4@0.46.1': - resolution: {integrity: sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis@0.51.0': resolution: {integrity: sha512-uL/GtBA0u72YPPehwOvthAe+Wf8k3T+XQPBssJmTYl6fzuZjNq8zTfxVFhl9nRFjFVEe+CtiYNT0Q3AyqW1Z0A==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4200,24 +4064,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-tedious@0.18.1': - resolution: {integrity: sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-tedious@0.22.0': resolution: {integrity: sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-undici@0.10.1': - resolution: {integrity: sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.7.0 - '@opentelemetry/instrumentation-undici@0.14.0': resolution: {integrity: sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4236,12 +4088,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.57.2': - resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.203.0': resolution: {integrity: sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4278,10 +4124,6 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/redis-common@0.36.2': - resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} - engines: {node: '>=14'} - '@opentelemetry/redis-common@0.38.0': resolution: {integrity: sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4316,12 +4158,6 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 - '@opentelemetry/resources@1.30.1': - resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/resources@2.0.1': resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4346,12 +4182,6 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@1.30.1': - resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.0.1': resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4364,20 +4194,10 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.28.0': - resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} - engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.34.0': resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} - '@opentelemetry/sql-common@0.40.1': - resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@opentelemetry/sql-common@0.41.0': resolution: {integrity: sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4405,11 +4225,6 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@prisma/instrumentation@6.7.0': - resolution: {integrity: sha512-3NuxWlbzYNevgPZbV0ktA2z6r0bfh0g22ONTxcK09a6+6MdIPjHsYx1Hnyu4yOq+j7LmupO5J69hhuOnuvj8oQ==} - peerDependencies: - '@opentelemetry/api': ^1.8 - '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -4509,7 +4324,7 @@ packages: version: 0.0.0 '@rush-temp/analytics-service@file:projects/analytics-service.tgz': - resolution: {integrity: sha512-WqRMrPPRa4DJz4t8SbPnBjc2GIApL3ZtEHrxz755As/02zLRa2jD4BpAzafLrYRFQ5ALM4P4lblmqq3Iqiodlg==, tarball: file:projects/analytics-service.tgz} + resolution: {integrity: sha512-SbyEMU4sni8FrKq4fRlrYnrAXKOEXR+MtJq2rqR1H7tR+VHaCNToj9Sb4duvYetvhoMfrcMrJfDFdzALTmcmgw==, tarball: file:projects/analytics-service.tgz} version: 0.0.0 '@rush-temp/analytics@file:projects/analytics.tgz': @@ -4673,7 +4488,7 @@ packages: version: 0.0.0 '@rush-temp/communication-resources@file:projects/communication-resources.tgz': - resolution: {integrity: sha512-zxeMDdG2DrlcLIt8khrcaTxv+zAKxW0R/ntdxzoMNtz3lVWcpMuLoB/Mfi0Z9AoyZh3SHtzMFMpaVYQAOxetVA==, tarball: file:projects/communication-resources.tgz} + resolution: {integrity: sha512-fbzeC4XwbwgeXRdRXsguOzBB7ioJUCr+k1xNqw9d7y7mxhz7rtDsZIF7Qj8xm7gs+P5y0L2gsiGiwe9OMDgAaw==, tarball: file:projects/communication-resources.tgz} version: 0.0.0 '@rush-temp/communication-rest-client@file:projects/communication-rest-client.tgz': @@ -4701,7 +4516,7 @@ packages: version: 0.0.0 '@rush-temp/communication@file:projects/communication.tgz': - resolution: {integrity: sha512-JYIsuNgjgvMF5Y4FxAMTWb4N56q+qM/dN3uU0Gj4LCeiM2AnsjYoIGC7iDaRqAWE4CU58X6UzXFGsjifBvF42g==, tarball: file:projects/communication.tgz} + resolution: {integrity: sha512-kO/x+NBE4Nmtz5KhHO0kxty/0N1A2PH4dNVTdOviEV4mVAhFm7DkgWHu0SwWJvQrCzsB0xEga3/CGuGjcPyMjA==, tarball: file:projects/communication.tgz} version: 0.0.0 '@rush-temp/contact-assets@file:projects/contact-assets.tgz': @@ -4921,7 +4736,7 @@ packages: version: 0.0.0 '@rush-temp/inbox-resources@file:projects/inbox-resources.tgz': - resolution: {integrity: sha512-GJv56ydws7RpaX+vD+FmfItXmAm0Jua2LH23yZU407BLKZZJ0xMPmhAS2vPFLnuqII9GZKC4Rxzd9LS0+ZvDSA==, tarball: file:projects/inbox-resources.tgz} + resolution: {integrity: sha512-nsScMKgG3wIAVWUJxam/R0SuwJT57iTxdljOSCllUjmvC2qMl+4nOOR6eAmd/6ucLm+hWf3a3ZpCECMHeWwZig==, tarball: file:projects/inbox-resources.tgz} version: 0.0.0 '@rush-temp/inbox@file:projects/inbox.tgz': @@ -5001,7 +4816,7 @@ packages: version: 0.0.0 '@rush-temp/measurements-otlp@file:projects/measurements-otlp.tgz': - resolution: {integrity: sha512-6fNyDKq1tDcefPNc+R4nrvNS9ZnML9RQ1W6DiJLU3IOo/kgpelMp8kLi/V5DN01KILQP3We2vanxjC5KJgllnA==, tarball: file:projects/measurements-otlp.tgz} + resolution: {integrity: sha512-OF0gOorTONul+LTXobiri6lEXqLNj5YcJnALqvFArHiEAdDnhu/2d9DqZF/+LyZ+SMtsEthL9UEbxTNb/v62Zg==, tarball: file:projects/measurements-otlp.tgz} version: 0.0.0 '@rush-temp/measurements@file:projects/measurements.tgz': @@ -5081,7 +4896,7 @@ packages: version: 0.0.0 '@rush-temp/model-communication@file:projects/model-communication.tgz': - resolution: {integrity: sha512-WsdabK4vJ00ZRzapzDxVTGmcyhh/p4gzWae5gYUo6TWgx3MzUIXSQjF9usuGtRjTZBCCSJOFzO6z/YjKw7ZyoQ==, tarball: file:projects/model-communication.tgz} + resolution: {integrity: sha512-9C2Dj1gqWlewdkiwF4iTO3PgZCuX9aHolQmvTu2fchby8RBwJVWr6QTRA69cGNekTZuXzsJaRRtpXg0BL9yopQ==, tarball: file:projects/model-communication.tgz} version: 0.0.0 '@rush-temp/model-contact@file:projects/model-contact.tgz': @@ -5528,6 +5343,10 @@ packages: resolution: {integrity: sha512-8tndpe/4V1VSqIisntC9lxmfP8oJd+6z3gd06BQHBertjGBezb/FE4+Qmxuj8CVVK/2Wqr4EZe3zYWB/YbErOA==, tarball: file:projects/pod-print.tgz} version: 0.0.0 + '@rush-temp/pod-process@file:projects/pod-process.tgz': + resolution: {integrity: sha512-CX94IUHO3sQtTelqy4CBPydusE7MTCJ/l26hsifScPSCKEZTc4+2Qz3aZSwo2SMLerH1ZrXATpDeI4l2sgOT0A==, tarball: file:projects/pod-process.tgz} + version: 0.0.0 + '@rush-temp/pod-server@file:projects/pod-server.tgz': resolution: {integrity: sha512-rFNxtRr6+lvwNoyrvZcRdFmK2rm2vzDihvAt+x/WvCdY4Aea2VWRXewj6IuNLp/YB7svgMSHnLOtWHKLzMSF5Q==, tarball: file:projects/pod-server.tgz} version: 0.0.0 @@ -6009,7 +5828,7 @@ packages: version: 0.0.0 '@rush-temp/setting-resources@file:projects/setting-resources.tgz': - resolution: {integrity: sha512-V1fxm4+9HCGwPWJLZ8x0yEwISALxBPsaRyOOf59hbn2hls6U15CrmDuC96uHngv0jBmA6sTfPtwQh7x6iKS05Q==, tarball: file:projects/setting-resources.tgz} + resolution: {integrity: sha512-WTrqgpE93g8zjpDYMxBcNlR57SdyerXgTwcm/ffH9Rq6g6jeci4d3Ccg866G/ByekstdqoKmL0PsayYuMzcR1g==, tarball: file:projects/setting-resources.tgz} version: 0.0.0 '@rush-temp/setting@file:projects/setting.tgz': @@ -6263,21 +6082,6 @@ packages: resolution: {integrity: sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==} engines: {node: '>=18'} - '@sentry/node@9.22.0': - resolution: {integrity: sha512-89r2p6n0xeT2eiqIB0WXgz/rJzUgiOgZex7NvYwzEGeP0GoteDIf0Kbth/gCOy1md/ngiW+0X+S2Ed/uRw4XEQ==} - engines: {node: '>=18'} - - '@sentry/opentelemetry@9.22.0': - resolution: {integrity: sha512-m6JI2LUCm4FT34OQgh7or2Y9chKn8BrqawNqu7BEqbsGADE5VPwtdu7DwPOD7pC6KN9lGHVf0bqS7197e8Kz/A==} - engines: {node: '>=18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.0.0 - '@opentelemetry/core': ^1.30.1 || ^2.0.0 - '@opentelemetry/instrumentation': ^0.57.1 || ^0.200.0 - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 - '@opentelemetry/semantic-conventions': ^1.30.0 - '@sentry/svelte@9.22.0': resolution: {integrity: sha512-ainvUpLPbTUKSxVwqcAnRJhAOzWFGsFGXNo6oEJ30pKSGhg3SrW7R5G3kwg7S7/MPr1/fWRPXjhAE1UGmzXIsA==} engines: {node: '>=18'} @@ -7131,9 +6935,6 @@ packages: '@types/multiparty@0.0.36': resolution: {integrity: sha512-DNqs3xYFMLgQfYrYuuleox1wzpUJxOgR8+WQ99DPu1B6zLT+sCMMsWfqEFTThPrI3109q81sChUKg/QM/ZUPiw==} - '@types/mysql@2.15.26': - resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} @@ -7191,9 +6992,6 @@ packages: '@types/pg@8.15.4': resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} - '@types/pg@8.6.1': - resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -7239,9 +7037,6 @@ packages: '@types/serve-static@1.15.5': resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} - '@types/shimmer@1.2.0': - resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} - '@types/snappyjs@0.7.1': resolution: {integrity: sha512-OxjzJ6cQZstysMh6PEwZWmK9qlKZyezHJKOkcUkZDooSFuog2votUEKkxMaTq51UQF3cJkXKQ+XGlj4FSl8JQQ==} @@ -13112,9 +12907,6 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - shimmer@1.2.1: - resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - side-channel@1.0.5: resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} engines: {node: '>= 0.4'} @@ -16428,10 +16220,6 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs@0.57.2': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api@1.9.0': {} '@opentelemetry/auto-instrumentations-node@0.62.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)': @@ -16490,19 +16278,10 @@ snapshots: - encoding - supports-color - '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16618,15 +16397,6 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-amqplib@0.46.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-amqplib@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16672,16 +16442,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.43.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@types/connect': 3.4.38 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-connect@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16700,13 +16460,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.16.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-dataloader@0.21.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16721,15 +16474,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.47.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-express@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16748,14 +16492,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.19.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-fs@0.23.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16764,13 +16500,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.43.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-generic-pool@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16778,13 +16507,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.47.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-graphql@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16800,15 +16522,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.45.2(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-hapi@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16828,26 +16541,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.57.2(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - forwarded-parse: 2.1.2 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-ioredis@0.47.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-ioredis@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16865,22 +16558,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.7.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-knex@0.44.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-knex@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16889,15 +16566,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.47.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-koa@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16907,13 +16575,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.44.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16930,14 +16591,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.52.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mongodb@0.56.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16946,15 +16599,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.46.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mongoose@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16964,15 +16608,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.45.2(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mysql2@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -16982,15 +16617,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.45.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@types/mysql': 2.15.26 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-mysql@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17025,18 +16651,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.51.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) - '@types/pg': 8.6.1 - '@types/pg-pool': 2.0.6 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-pg@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17058,15 +16672,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis-4@0.46.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.34.0 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-redis@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17108,15 +16713,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.18.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@types/tedious': 4.0.14 - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-tedious@0.22.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17126,14 +16722,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.10.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/instrumentation-undici@0.14.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17159,18 +16747,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.57.2 - '@types/shimmer': 1.2.0 - import-in-the-middle: 1.13.2 - require-in-the-middle: 7.4.0 - semver: 7.6.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color - '@opentelemetry/otlp-exporter-base@0.203.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17210,8 +16786,6 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common@0.36.2': {} - '@opentelemetry/redis-common@0.38.0': {} '@opentelemetry/resource-detector-alibaba-cloud@0.31.3(@opentelemetry/api@1.9.0)': @@ -17253,12 +16827,6 @@ snapshots: - encoding - supports-color - '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17306,13 +16874,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17327,15 +16888,8 @@ snapshots: '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.34.0': {} - '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17360,13 +16914,6 @@ snapshots: '@popperjs/core@2.11.8': {} - '@prisma/instrumentation@6.7.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - transitivePeerDependencies: - - supports-color - '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -17891,7 +17438,6 @@ snapshots: '@rush-temp/analytics-service@file:projects/analytics-service.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3))': dependencies: - '@sentry/node': 9.22.0 '@types/jest': 29.5.12 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) @@ -21723,7 +21269,6 @@ snapshots: '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': 0.203.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) - '@sentry/node': 9.22.0 '@types/jest': 29.5.12 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) @@ -24570,6 +24115,38 @@ snapshots: - supports-color - utf-8-validate + '@rush-temp/pod-process@file:projects/pod-process.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))': + dependencies: + '@tsconfig/node16': 1.0.4 + '@types/jest': 29.5.12 + '@types/node': 22.15.29 + '@types/uuid': 8.3.4 + '@types/ws': 8.5.11 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) + dotenv: 16.0.3 + esbuild: 0.24.2 + eslint: 8.56.0 + eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.8.3) + eslint-plugin-import: 2.29.1(eslint@8.56.0) + eslint-plugin-n: 15.7.0(eslint@8.56.0) + eslint-plugin-node: 11.1.0(eslint@8.56.0) + eslint-plugin-promise: 6.1.1(eslint@8.56.0) + jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3)) + prettier: 3.2.5 + ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3) + ts-node: 10.9.2(@types/node@22.15.29)(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - '@babel/core' + - '@jest/types' + - '@swc/core' + - '@swc/wasm' + - babel-jest + - babel-plugin-macros + - node-notifier + - supports-color + '@rush-temp/pod-server@file:projects/pod-server.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))': dependencies: '@types/body-parser': 1.19.5 @@ -29847,55 +29424,6 @@ snapshots: '@sentry/core@9.22.0': {} - '@sentry/node@9.22.0': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-amqplib': 0.46.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.43.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-dataloader': 0.16.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.47.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fs': 0.19.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-generic-pool': 0.43.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-graphql': 0.47.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-hapi': 0.45.2(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-ioredis': 0.47.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-kafkajs': 0.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-knex': 0.44.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-koa': 0.47.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-lru-memoizer': 0.44.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongodb': 0.52.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongoose': 0.46.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql': 0.45.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql2': 0.45.2(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-pg': 0.51.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-redis-4': 0.46.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-tedious': 0.18.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-undici': 0.10.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@prisma/instrumentation': 6.7.0(@opentelemetry/api@1.9.0) - '@sentry/core': 9.22.0 - '@sentry/opentelemetry': 9.22.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0) - import-in-the-middle: 1.13.2 - minimatch: 9.0.5 - transitivePeerDependencies: - - supports-color - - '@sentry/opentelemetry@9.22.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.34.0 - '@sentry/core': 9.22.0 - '@sentry/svelte@9.22.0(svelte@4.2.19)': dependencies: '@sentry/browser': 9.22.0 @@ -30944,10 +30472,6 @@ snapshots: dependencies: '@types/node': 22.15.29 - '@types/mysql@2.15.26': - dependencies: - '@types/node': 22.15.29 - '@types/mysql@2.15.27': dependencies: '@types/node': 22.15.29 @@ -31029,12 +30553,6 @@ snapshots: pg-protocol: 1.7.0 pg-types: 2.2.0 - '@types/pg@8.6.1': - dependencies: - '@types/node': 22.15.29 - pg-protocol: 1.7.0 - pg-types: 2.2.0 - '@types/plist@3.0.5': dependencies: '@types/node': 22.15.29 @@ -31089,8 +30607,6 @@ snapshots: '@types/mime': 3.0.4 '@types/node': 22.15.29 - '@types/shimmer@1.2.0': {} - '@types/snappyjs@0.7.1': {} '@types/sockjs@0.3.36': @@ -38011,8 +37527,6 @@ snapshots: shell-quote@1.8.1: {} - shimmer@1.2.1: {} - side-channel@1.0.5: dependencies: call-bind: 1.0.7 diff --git a/common/scripts/docker.sh b/common/scripts/docker.sh index 7ef29a509f..8df3dd1e8f 100755 --- a/common/scripts/docker.sh +++ b/common/scripts/docker.sh @@ -24,4 +24,5 @@ rush docker:build -p 20 \ --to @hcengineering/pod-external \ --to @hcengineering/pod-backup \ --to @hcengineering/backup-api-pod \ ---to @hcengineering/pod-billing +--to @hcengineering/pod-billing \ +--to @hcengineering/pod-process diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index f9e487ac6a..132822ca9b 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -502,6 +502,21 @@ services: - HULY_TOKEN_SECRET=secret - HULY_ACCOUNT_SERVICE=http://huly.local:3000 restart: unless-stopped + process-service: + image: hardcoreeng/process + extra_hosts: + - 'huly.local:host-gateway' + depends_on: + redpanda: + condition: service_started + account: + condition: service_started + environment: + - SECRET=secret + - ACCOUNTS_URL=http://huly.local:3000 + - QUEUE_CONFIG=${QUEUE_CONFIG} + - QUEUE_REGION=cockroach + restart: unless-stopped backup-cockroach: image: hardcoreeng/backup extra_hosts: diff --git a/models/process/src/index.ts b/models/process/src/index.ts index 6da7ba0a07..ce730c1e56 100644 --- a/models/process/src/index.ts +++ b/models/process/src/index.ts @@ -109,6 +109,8 @@ export class TTrigger extends TDoc implements Trigger { checkFunction?: Resource init!: boolean + + auto?: boolean } @Model(process.class.Transition, core.class.Doc, DOMAIN_MODEL) @@ -1050,7 +1052,8 @@ export function createModel (builder: Builder): void { presenter: process.component.CardUpdatePresenter, requiredParams: [], checkFunction: process.triggerCheck.UpdateCheck, - init: false + init: false, + auto: true }, process.trigger.OnCardUpdate ) @@ -1062,7 +1065,8 @@ export function createModel (builder: Builder): void { label: process.string.OnSubProcessesDone, icon: process.icon.WaitSubprocesses, requiredParams: [], - init: false + init: false, + auto: true }, process.trigger.OnSubProcessesDone ) diff --git a/models/server-process/src/index.ts b/models/server-process/src/index.ts index 4758e82ac9..7fc26d96ef 100644 --- a/models/server-process/src/index.ts +++ b/models/server-process/src/index.ts @@ -19,6 +19,7 @@ import type { Resource } from '@hcengineering/platform' import process, { ExecutionStatus, type CheckFunc } from '@hcengineering/process' import serverCore from '@hcengineering/server-core' import serverProcess, { + type RollbackFunc, type ExecuteFunc, type FuncImpl, type MethodImpl, @@ -41,23 +42,31 @@ export class TFuncImpl extends TProcessFunction implements FuncImpl { @Mixin(serverProcess.mixin.TriggerImpl, process.class.Trigger) export class TTriggerImpl extends TTrigger implements TriggerImpl { serverCheckFunc?: Resource + rollbackFunc?: Resource + preventRollback?: boolean } export function createModel (builder: Builder): void { builder.createModel(TMethodImpl, TFuncImpl, TTriggerImpl) builder.mixin(process.trigger.OnToDoClose, process.class.Trigger, serverProcess.mixin.TriggerImpl, { - serverCheckFunc: serverProcess.func.CheckToDo + serverCheckFunc: serverProcess.func.CheckToDo, + rollbackFunc: serverProcess.rollbacks.ToDoCloseRollback }) builder.mixin(process.trigger.OnToDoRemove, process.class.Trigger, serverProcess.mixin.TriggerImpl, { - serverCheckFunc: serverProcess.func.CheckToDo + serverCheckFunc: serverProcess.func.CheckToDo, + rollbackFunc: serverProcess.rollbacks.ToDoCancellRollback }) builder.mixin(process.trigger.OnCardUpdate, process.class.Trigger, serverProcess.mixin.TriggerImpl, { serverCheckFunc: serverProcess.func.OnCardUpdateCheck }) + builder.mixin(process.trigger.OnSubProcessesDone, process.class.Trigger, serverProcess.mixin.TriggerImpl, { + preventRollback: true + }) + builder.mixin(process.method.RunSubProcess, process.class.Method, serverProcess.mixin.MethodImpl, { func: serverProcess.func.RunSubProcess }) @@ -243,16 +252,6 @@ export function createModel (builder: Builder): void { } }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverProcess.trigger.OnExecutionTransition, - txMatch: { - _class: core.class.TxUpdateDoc, - objectClass: process.class.Execution, - 'operations.currentState': { $exists: true } - }, - isAsync: true - }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverProcess.trigger.OnProcessRemove, txMatch: { diff --git a/packages/api-client/src/rest/rest.ts b/packages/api-client/src/rest/rest.ts index 48a0061130..827e2dbbf3 100644 --- a/packages/api-client/src/rest/rest.ts +++ b/packages/api-client/src/rest/rest.ts @@ -215,8 +215,11 @@ export class RestClientImpl implements RestClient { return result } - async getModel (): Promise<{ hierarchy: Hierarchy, model: ModelDb }> { - const requestUrl = concatLink(this.endpoint, `/api/v1/load-model/${this.workspace}`) + async getModel (full: boolean = false): Promise<{ hierarchy: Hierarchy, model: ModelDb }> { + const requestUrl = new URL(concatLink(this.endpoint, `/api/v1/load-model/${this.workspace}`)) + if (full) { + requestUrl.searchParams.append('full', 'true') + } await this.checkRate() const result = await withRetry<{ hierarchy: Hierarchy, model: ModelDb, error?: Status }>(async () => { const response = await fetch(requestUrl, this.requestInit()) @@ -232,7 +235,7 @@ export class RestClientImpl implements RestClient { const model = new ModelDb(hierarchy) const ctx = new MeasureMetricsContext('loadModel', {}) - buildModel(ctx, modelResponse, (txes: Tx[]) => txes, hierarchy, model) + buildModel(ctx, modelResponse, undefined, hierarchy, model) return { hierarchy, model } }, isRLE) diff --git a/packages/api-client/src/rest/tx.ts b/packages/api-client/src/rest/tx.ts index e84e0039fd..b090c50f2d 100644 --- a/packages/api-client/src/rest/tx.ts +++ b/packages/api-client/src/rest/tx.ts @@ -42,12 +42,13 @@ import { RestClientImpl } from './rest' export async function createRestTxOperations ( endpoint: string, workspaceId: string, - token: string + token: string, + fullModel: boolean = false ): Promise { const restClient = new RestClientImpl(endpoint, workspaceId, token) const account = await restClient.getAccount() - const { hierarchy, model } = await restClient.getModel() + const { hierarchy, model } = await restClient.getModel(fullModel) return new TxOperations(new RestTxClient(restClient, hierarchy, model, account), account.socialIds[0]) } diff --git a/plugins/card-resources/src/components/ChangeType.svelte b/plugins/card-resources/src/components/ChangeType.svelte index c41e775a31..ae4e186d50 100644 --- a/plugins/card-resources/src/components/ChangeType.svelte +++ b/plugins/card-resources/src/components/ChangeType.svelte @@ -17,7 +17,7 @@ import { Card, CardEvents, MasterTag } from '@hcengineering/card' import { AnyAttribute, fillDefaults, Ref } from '@hcengineering/core' import { Card as CardModal, getClient } from '@hcengineering/presentation' - import { Label } from '@hcengineering/ui' + import ui, { Label } from '@hcengineering/ui' import { deepEqual } from 'fast-equals' import { createEventDispatcher } from 'svelte' import card from '../plugin' @@ -79,6 +79,7 @@ -{:else if value === ExecutionLogAction.Transition} - -{:else} +{:else if value === ExecutionLogAction.Rollback} {/if} diff --git a/plugins/process-resources/src/components/NotifierExtension.svelte b/plugins/process-resources/src/components/NotifierExtension.svelte index aef54aa668..21a0bef2ba 100644 --- a/plugins/process-resources/src/components/NotifierExtension.svelte +++ b/plugins/process-resources/src/components/NotifierExtension.svelte @@ -62,7 +62,8 @@ { event }, - event.action === ExecutionLogAction.Rollback ? NotificationSeverity.Warning : NotificationSeverity.Info + event.action === ExecutionLogAction.Rollback ? NotificationSeverity.Warning : NotificationSeverity.Info, + event.execution ) } diff --git a/plugins/process/src/index.ts b/plugins/process/src/index.ts index 7aea0ae9f2..cc5a48bbe5 100644 --- a/plugins/process/src/index.ts +++ b/plugins/process/src/index.ts @@ -56,6 +56,7 @@ export interface Trigger extends Doc { presenter?: AnyComponent checkFunction?: Resource init: boolean + auto?: boolean } export interface Transition extends Doc { @@ -81,7 +82,11 @@ export enum ExecutionLogAction { Rollback = 'rollback' } -export type CheckFunc = (params: Record, doc: Doc, hierarchy: Hierarchy) => Promise +export type CheckFunc = ( + params: Record, + context: Record, + hierarchy: Hierarchy +) => Promise export enum ExecutionStatus { Active = 'active', @@ -203,7 +208,8 @@ export default plugin(processId, { OnSubProcessesDone: '' as Ref, OnToDoClose: '' as Ref, OnToDoRemove: '' as Ref, - OnExecutionStart: '' as Ref + OnExecutionStart: '' as Ref, + OnExecutionContinue: '' as Ref }, triggerCheck: { ToDo: '' as Resource, diff --git a/pods/server/src/rpc.ts b/pods/server/src/rpc.ts index 4cd59d6de8..c694000f8f 100644 --- a/pods/server/src/rpc.ts +++ b/pods/server/src/rpc.ts @@ -294,25 +294,30 @@ export function registerRPC (app: Express, sessions: SessionManager, ctx: Measur const lastHash = req.query.lastHash as string const result = await session.loadModelRaw(ctx, lastModelTx, lastHash) const txes = Array.isArray(result) ? result : result.transactions - // we need to filter only hierarchy related txes. - const allowedClasess: Ref>[] = [ - core.class.Class, - core.class.Attribute, - core.class.Mixin, - core.class.Type, - core.class.Status, - core.class.Permission, - core.class.Space, - core.class.Tx - ] - const h = ctx.pipeline.context.hierarchy - const filtered = txes.filter( - (it) => - TxProcessor.isExtendsCUD(it._class) && - allowedClasess.some((cl) => h.isDerived((it as TxCUD).objectClass, cl)) - ) + const shouldFilter = req.query.full !== 'true' + if (shouldFilter) { + // we need to filter only hierarchy related txes. + const allowedClasess: Ref>[] = [ + core.class.Class, + core.class.Attribute, + core.class.Mixin, + core.class.Type, + core.class.Status, + core.class.Permission, + core.class.Space, + core.class.Tx + ] + const h = ctx.pipeline.context.hierarchy + const filtered = txes.filter( + (it) => + TxProcessor.isExtendsCUD(it._class) && + allowedClasess.some((cl) => h.isDerived((it as TxCUD).objectClass, cl)) + ) - await sendJson(req, res, filtered, rateLimitToHeaders(rateLimit)) + await sendJson(req, res, filtered, rateLimitToHeaders(rateLimit)) + } else { + await sendJson(req, res, txes, rateLimitToHeaders(rateLimit)) + } }) }) diff --git a/rush.json b/rush.json index 051cfb0140..460acabfdf 100644 --- a/rush.json +++ b/rush.json @@ -2652,6 +2652,11 @@ "packageName": "@hcengineering/model-billing", "projectFolder": "models/billing", "shouldPublish": false + }, + { + "packageName": "@hcengineering/pod-process", + "projectFolder": "services/process", + "shouldPublish": false } ] } diff --git a/server-plugins/process-resources/src/functions.ts b/server-plugins/process-resources/src/functions.ts new file mode 100644 index 0000000000..a5e5559da8 --- /dev/null +++ b/server-plugins/process-resources/src/functions.ts @@ -0,0 +1,212 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import cardPlugin, { Card, MasterTag } from '@hcengineering/card' +import core, { Association, Data, Doc, generateId, Hierarchy, matchQuery, Ref, Relation, Tx } from '@hcengineering/core' +import process, { + Execution, + ExecutionContext, + ExecutionStatus, + MethodParams, + Process, + processError, + ProcessToDo +} from '@hcengineering/process' +import { ExecuteResult, ProcessControl } from '@hcengineering/server-process' +import time, { ToDoPriority } from '@hcengineering/time' + +export function CheckToDo (params: Record, context: Record): boolean { + if (params._id === undefined) return false + if (context.todo === undefined) return false + return context.todo._id === params._id +} + +export function OnCardUpdateCheck ( + params: Record, + context: Record, + hierarchy: Hierarchy +): boolean { + if (context.card === undefined) return false + const res = matchQuery([context.card], params, context.card._class, hierarchy, true) + return res.length > 0 +} + +export async function AddRelation ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise { + const _id = generateId() + const association = params.association as Ref + if (isEmpty(association)) { + throw processError(process.error.RequiredParamsNotProvided, { params: 'association' }) + } + if (isEmpty(params._id)) { + throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) + } + if (isEmpty(params.direction)) { + throw processError(process.error.RequiredParamsNotProvided, { params: 'direction' }) + } + const targetId = params._id as Ref + const direction = params.direction as 'A' | 'B' + const docA = direction === 'A' ? targetId : execution.card + const docB = direction === 'A' ? execution.card : targetId + const data: Data = { + association, + docA, + docB + } + const res: Tx[] = [control.client.txFactory.createTxCreateDoc(core.class.Relation, core.space.Workspace, data, _id)] + const rollback: Tx[] = [control.client.txFactory.createTxRemoveDoc(core.class.Relation, core.space.Workspace, _id)] + return { txes: res, rollback, context: _id } +} + +export async function UpdateCard ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise { + if (Object.keys(params).length === 0) return + const target = control.cache.get(execution.card) + if (target === undefined) return + const update: Record = {} + const prevValue: Record = {} + for (const key in params) { + prevValue[key] = target[key] + update[key] = (params as any)[key] + } + const res: Tx[] = [control.client.txFactory.createTxUpdateDoc(target._class, target.space, target._id, update)] + const rollback: Tx[] = [ + control.client.txFactory.createTxUpdateDoc(target._class, target.space, target._id, prevValue) + ] + return { txes: res, rollback, context: null } +} + +export async function RunSubProcess ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise { + if (params._id === undefined) return + const card = params.card ?? execution.card + const processId = params._id as Ref + const target = control.client.getModel().findObject(processId) + if (target === undefined) return + const res: Tx[] = [] + const context: Ref[] = [] + for (const _card of Array.isArray(card) ? card : [card]) { + if (target.parallelExecutionForbidden === true) { + const currentExecution = await control.client.findAll(process.class.Execution, { + process: target._id, + card: _card, + done: false + }) + if (currentExecution.length > 0) { + // todo, show erro after merge another pr + continue + } + } + const initTransition = control.client + .getModel() + .findAllSync(process.class.Transition, { process: target._id, from: null })[0] + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const emptyContext = {} as ExecutionContext + const id = generateId() + res.push( + control.client.txFactory.createTxCreateDoc( + process.class.Execution, + core.space.Workspace, + { + process: processId, + currentState: initTransition.to, + card: _card, + context: emptyContext, + status: ExecutionStatus.Active, + rollback: [], + parentId: execution._id + }, + id + ) + ) + context.push(id) + } + return { txes: res, rollback: undefined, context } +} + +export async function CreateToDo ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise { + for (const key in { user: params.user, title: params.title }) { + const val = (params as any)[key] + if (isEmpty(val)) { + throw processError(process.error.RequiredParamsNotProvided, { params: key }) + } + } + if (params.user === undefined || params.title === undefined) return + const res: Tx[] = [] + const rollback: Tx[] = [] + const id = generateId() + res.push( + control.client.txFactory.createTxCreateDoc( + process.class.ProcessToDo, + time.space.ToDos, + { + attachedTo: execution.card, + attachedToClass: cardPlugin.class.Card, + collection: 'todos', + workslots: 0, + execution: execution._id, + title: params.title, + user: params.user, + description: params.description ?? '', + dueDate: params.dueDate, + priority: params.priority ?? ToDoPriority.NoPriority, + visibility: 'public', + rank: '', + withRollback: params.withRollback ?? false + }, + id + ) + ) + return { txes: res, rollback, context: id } +} + +export async function CreateCard ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise { + const { _class, title, ...attrs } = params + for (const key in { _class, title }) { + const val = (params as any)[key] + if (isEmpty(val)) { + throw processError(process.error.RequiredParamsNotProvided, { params: key }) + } + } + const _id = generateId() + const data = { + title, + ...attrs + } as any + const res: Tx[] = [control.client.txFactory.createTxCreateDoc(_class as Ref, execution.space, data, _id)] + const rollback: Tx[] = [control.client.txFactory.createTxRemoveDoc(_class as Ref, execution.space, _id)] + return { txes: res, rollback, context: _id } +} + +function isEmpty (value: any): boolean { + return value === undefined || value === null || (typeof value === 'string' && value.trim() === '') +} diff --git a/server-plugins/process-resources/src/index.ts b/server-plugins/process-resources/src/index.ts index ec9a477007..b8610d5391 100644 --- a/server-plugins/process-resources/src/index.ts +++ b/server-plugins/process-resources/src/index.ts @@ -13,57 +13,20 @@ // limitations under the License. // -import cardPlugin, { Card, MasterTag } from '@hcengineering/card' -import contact, { Employee } from '@hcengineering/contact' -import core, { - ArrOf, - Association, - Data, - Doc, - generateId, - getObjectValue, - Hierarchy, - matchQuery, - Ref, - RefTo, - Relation, - Tx, - TxCreateDoc, - TxCUD, - TxProcessor, - TxRemoveDoc, - TxUpdateDoc -} from '@hcengineering/core' -import { getEmbeddedLabel, getResource } from '@hcengineering/platform' +import cardPlugin, { Card } from '@hcengineering/card' +import core, { Tx, TxCreateDoc, TxCUD, TxProcessor, TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core' import process, { ContextId, Execution, - ExecutionContext, - ExecutionError, - ExecutionLogAction, - ExecutionStatus, - MethodParams, - parseContext, - parseError, Process, ProcessContext, - processError, - ProcessError, ProcessToDo, - SelectedContext, - SelectedContextFunc, SelectedExecutonContext, - SelectedNested, - SelectedRelation, - SelectedUserRequest, State, - Step, Transition } from '@hcengineering/process' -import { TriggerControl } from '@hcengineering/server-core' -import serverProcess, { ExecuteResult, MethodImpl } from '@hcengineering/server-process' -import time, { ToDoPriority } from '@hcengineering/time' -import { isError } from './errors' +import { QueueTopic, TriggerControl } from '@hcengineering/server-core' +import { ProcessMessage } from '@hcengineering/server-process' import { Absolute, Add, @@ -85,17 +48,43 @@ import { Prepend, Random, Remove, + RemoveFirst, + RemoveLast, Replace, ReplaceAll, + RoleContext, Round, Split, Subtract, - RemoveFirst, - RemoveLast, Trim, UpperCase } from './transform' -import { getAttributeValue, pickTransition } from './utils' +import { + RunSubProcess, + CreateToDo, + UpdateCard, + CreateCard, + AddRelation, + CheckToDo, + OnCardUpdateCheck +} from './functions' +import { ToDoCancellRollback, ToDoCloseRollback } from './rollback' + +async function putEventToQueue (value: Omit, control: TriggerControl): Promise { + if (control.queue === undefined) return + const producer = control.queue.getProducer(control.ctx.newChild('queue', {}), QueueTopic.Process) + + try { + await producer.send(control.workspace.uuid, [ + { + ...value, + account: control.txFactory.account + } + ]) + } catch (err) { + control.ctx.error('Could not queue process event', { err, value }) + } +} export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl): Promise { const res: Tx[] = [] @@ -108,482 +97,16 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl): await control.findAll(control.ctx, process.class.ProcessToDo, { _id: updateTx.objectId }, { limit: 1 }) )[0] if (todo === undefined) continue - const execution = ( - await control.findAll(control.ctx, process.class.Execution, { _id: todo.execution }, { limit: 1 }) - )[0] - if (execution === undefined) continue - const _process = await control.modelDb.findOne(process.class.Process, { _id: execution.process }) - if (_process === undefined) continue - const transitions = control.modelDb.findAllSync(process.class.Transition, { - from: execution.currentState, - process: _process._id, - trigger: process.trigger.OnToDoClose - }) - const transition = await pickTransition(control, execution, transitions, todo) - if (transition === undefined) continue - const rollback: Tx[] = [control.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { doneOn: null })] - res.push(...(await executeTransition(execution, transition, control, rollback))) - } - return res -} - -async function executeTransition ( - execution: Execution, - transition: Transition, - control: TriggerControl, - rollback: Tx[], - disableRollback = false -): Promise { - let deep = control.contextCache.get(execution._id + 'transition') ?? 1 - deep++ - control.contextCache.set(execution._id + 'transition', deep) - if (deep > 100) { - const error = parseError( - processError(process.error.TooDeepTransitionRecursion, undefined, undefined, true), - transition._id - ) - return [control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { error: [error] })] - } - const res: Tx[] = [] - const _process = control.modelDb.findObject(execution.process) - if (_process === undefined) return res - - const state = control.modelDb.findObject(transition.to) - if (state === undefined) return res - const isDone = - control.modelDb.findAllSync(process.class.Transition, { - from: transition.to, - process: transition.process - }).length === 0 - const errors: ExecutionError[] = [] - if (execution.currentState !== null) { - rollback.push( - control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { - currentState: execution.currentState, - status: execution.status - }) - ) - } else { - rollback.push(control.txFactory.createTxRemoveDoc(execution._class, execution.space, execution._id)) - } - for (const action of transition.actions) { - const actionResult = await executeAction(action, transition._id, execution, control) - if (isError(actionResult)) { - errors.push(actionResult) - } else { - if (actionResult.rollback !== undefined) { - rollback.push(...actionResult.rollback) - } - res.push(...actionResult.txes) - } - } - if (!disableRollback) { - execution.rollback.push(rollback) - } - if (isDone && execution.parentId !== undefined) { - const parentWaitTxes = await checkParent(execution, control) - if (parentWaitTxes !== undefined) { - res.push(...parentWaitTxes) - } - } - res.push( - control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { - rollback: execution.rollback, - context: execution.context, - currentState: state._id, - status: isDone ? ExecutionStatus.Done : ExecutionStatus.Active - }) - ) - res.push( - control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, { - execution: execution._id, - process: execution.process, - card: execution.card, - transition: transition._id, - action: ExecutionLogAction.Transition - }) - ) - if (errors.length === 0) { - return res - } else { - return [control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { error: errors })] - } -} - -async function executeAction ( - action: Step, - transition: Ref, - execution: Execution, - control: TriggerControl -): Promise { - try { - const method = control.modelDb.findObject(action.methodId) - if (method === undefined) throw processError(process.error.MethodNotFound, { methodId: action.methodId }, {}, true) - const impl = control.hierarchy.as(method, serverProcess.mixin.MethodImpl) as MethodImpl - if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: action.methodId }, {}, true) - const params = await fillParams(action.params, execution, control) - const f = await getResource(impl.func) - const res = await f(params, execution, control) - if (!isError(res) && action.context?._id != null && res.context != null) { - execution.context[action.context._id] = res.context - } - return res - } catch (err) { - if (err instanceof ProcessError) { - if (err.shouldLog) { - control.ctx.error(err.message, { props: err.props }) - } - return parseError(err, transition) - } else { - const errorId = generateId() - control.ctx.error(err instanceof Error ? err.message : String(err), { errorId }) - return parseError(processError(process.error.InternalServerError, { errorId }), transition) - } - } -} - -async function fillValue ( - value: any, - context: SelectedContext, - control: TriggerControl, - execution: Execution -): Promise { - for (const func of context.functions ?? []) { - const transform = control.modelDb.findObject(func.func) - if (transform === undefined) throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true) - if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) { - throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true) - } - const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl) - const f = await getResource(funcImpl.func) - value = await f(value, func.props, control, execution) - } - return value -} - -async function getNestedValue ( - control: TriggerControl, - execution: Execution, - context: SelectedNested -): Promise { - const cardValue = await control.findAll(control.ctx, cardPlugin.class.Card, { _id: execution.card }, { limit: 1 }) - if (cardValue.length === 0) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true) - const attr = control.hierarchy.findAttribute(cardValue[0]._class, context.path) - if (attr === undefined) throw processError(process.error.AttributeNotExists, { key: context.path }) - const nestedValue = getObjectValue(context.path, cardValue[0]) - if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label }) - const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf).of : attr.type - const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo).to : parentType._class - const target = await control.findAll(control.ctx, targetClass, { - _id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] } - }) - if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label }) - const nested = control.hierarchy.findAttribute(targetClass, context.key) - if (context.sourceFunction !== undefined) { - const transform = control.modelDb.findObject(context.sourceFunction) - if (transform === undefined) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl) - const f = await getResource(funcImpl.func) - const reduced = await f(target, {}, control, execution) - const val = getObjectValue(context.key, reduced) - if (val == null) { - throw processError( - process.error.EmptyRelatedObjectValue, - {}, - { parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) } - ) - } - return val - } - const val = getObjectValue(context.key, target[0]) - if (val == null) { - throw processError( - process.error.EmptyRelatedObjectValue, - {}, - { parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) } - ) - } - return val -} - -async function getRelationValue ( - control: TriggerControl, - execution: Execution, - context: SelectedRelation -): Promise { - const assoc = control.modelDb.findObject(context.association) - if (assoc === undefined) throw processError(process.error.RelationNotExists, {}) - const targetClass = context.direction === 'A' ? assoc.classA : assoc.classB - const q = context.direction === 'A' ? { docB: execution.card } : { docA: execution.card } - const relations = await control.findAll(control.ctx, core.class.Relation, { association: assoc._id, ...q }) - const name = context.direction === 'A' ? assoc.nameA : assoc.nameB - if (relations.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: name }) - const ids = relations.map((it) => { - return context.direction === 'A' ? it.docA : it.docB - }) - const target = await control.findAll(control.ctx, targetClass, { _id: { $in: ids } }) - if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name }) - const attr = context.key !== '' ? control.hierarchy.findAttribute(targetClass, context.key) : undefined - if (context.sourceFunction !== undefined) { - const transform = control.modelDb.findObject(context.sourceFunction) - if (transform === undefined) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl) - const f = await getResource(funcImpl.func) - const reduced = await f(target, {}, control, execution) - const val = Array.isArray(reduced) - ? reduced.map((v) => getObjectValue(context.key, v)) - : getObjectValue(context.key, reduced) - if (val == null) { - throw processError( - process.error.EmptyRelatedObjectValue, - { parent: name }, - { attr: attr?.label ?? getEmbeddedLabel(context.name) } - ) - } - return val - } - const val = - Array.isArray(target) && target.length > 1 - ? target.map((v) => getObjectValue(context.key, v)) - : getObjectValue(context.key, target[0]) - if (val == null) { - throw processError( - process.error.EmptyRelatedObjectValue, - { parent: name }, - { attr: attr?.label ?? getEmbeddedLabel(context.name) } - ) - } - return val -} - -async function fillParams ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise> { - const res: MethodParams = {} - for (const key in params) { - const value = (params as any)[key] - const valueResult = await getContextValue(value, control, execution) - ;(res as any)[key] = valueResult - } - return res -} - -async function getContextValue (value: any, control: TriggerControl, execution: Execution): Promise { - const context = parseContext(value) - if (context !== undefined) { - let value: any | undefined - try { - if (context.type === 'attribute') { - value = await getAttributeValue(control, execution, context) - } else if (context.type === 'relation') { - value = await getRelationValue(control, execution, context) - } else if (context.type === 'nested') { - value = await getNestedValue(control, execution, context) - } else if (context.type === 'userRequest') { - value = getUserRequestValue(control, execution, context) - } else if (context.type === 'function') { - value = await getFunctionValue(control, execution, context) - } else if (context.type === 'context') { - value = getExecutionContextValue(control, execution, context) - } - return await fillValue(value, context, control, execution) - } catch (err: any) { - if (err instanceof ProcessError && context.fallbackValue !== undefined) { - return await fillValue(context.fallbackValue, context, control, execution) - } - throw err - } - } else { - return value - } -} - -async function getFunctionValue ( - control: TriggerControl, - execution: Execution, - context: SelectedContextFunc -): Promise { - const func = control.modelDb.findObject(context.func) - if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true) - const impl = control.hierarchy.as(func, serverProcess.mixin.FuncImpl) - if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true) - const f = await getResource(impl.func) - const res = await f(null, context.props, control, execution) - if (context.sourceFunction !== undefined) { - const transform = control.modelDb.findObject(context.sourceFunction) - if (transform === undefined) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) { - throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) - } - const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl) - const f = await getResource(funcImpl.func) - const val = await f(res, {}, control, execution) - if (val == null) { - throw processError(process.error.EmptyFunctionResult, {}, { func: func.label }) - } - return val - } - if (res == null) { - throw processError(process.error.EmptyFunctionResult, {}, { func: func.label }) - } - return res -} - -function getUserRequestValue (control: TriggerControl, execution: Execution, context: SelectedUserRequest): any { - const userContext = execution.context[context.id] - if (userContext !== undefined) return userContext - const attr = control.hierarchy.findAttribute(context._class, context.key) - throw processError( - process.error.UserRequestedValueNotProvided, - {}, - { attr: attr?.label ?? getEmbeddedLabel(context.key) } - ) -} - -function getExecutionContextValue ( - control: TriggerControl, - execution: Execution, - context: SelectedExecutonContext -): any { - const userContext = execution.context[context.id] - if (userContext !== undefined) return userContext - const _process = control.modelDb.findObject(execution.process) - if (_process === undefined) return - const ctx = _process.context[context.id] - if (ctx === undefined) return - throw processError(process.error.ContextValueNotProvided, { name: ctx.name }) -} - -async function initState (execution: Execution, control: TriggerControl): Promise { - const _process = control.modelDb.findObject(execution.process) - if (_process === undefined) return [] - const transition = control.modelDb.findAllSync(process.class.Transition, { - process: execution.process, - from: null - })[0] - if (transition === undefined) return [] - const errors: ExecutionError[] = [] - const res: Tx[] = [] - const rollback: Tx[] = [] - rollback.push(control.txFactory.createTxRemoveDoc(execution._class, execution.space, execution._id)) - for (const action of transition.actions) { - const actionResult = await executeAction(action, transition._id, execution, control) - if (isError(actionResult)) { - errors.push(actionResult) - } else { - if (actionResult.rollback !== undefined) { - rollback.push(...actionResult.rollback) - } - res.push(...actionResult.txes) - } - } - execution.rollback.push(rollback) - res.push( - control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { - currentState: transition.to, - rollback: execution.rollback, - context: execution.context, - status: ExecutionStatus.Active - }) - ) - res.push( - control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, { - execution: execution._id, - process: execution.process, - card: execution.card, - transition: transition._id, - action: ExecutionLogAction.Started - }) - ) - if (errors.length === 0) { - return res - } else { - return [control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { error: errors })] - } -} - -async function checkParent (execution: Execution, control: TriggerControl): Promise { - const subProcesses = await control.findAll(control.ctx, process.class.Execution, { - parentId: execution.parentId, - status: ExecutionStatus.Active - }) - const filtered = subProcesses.filter((it) => it._id !== execution._id) - if (filtered.length !== 0) return - const parent = (await control.findAll(control.ctx, process.class.Execution, { _id: execution.parentId }))[0] - if (parent === undefined) return - const res: Tx[] = [] - const _process = control.modelDb.findObject(parent.process) - if (_process === undefined) return res - if (parent.status !== ExecutionStatus.Active) return res - const transitions = control.modelDb.findAllSync(process.class.Transition, { - from: parent.currentState, - process: parent.process, - trigger: process.trigger.OnSubProcessesDone - }) - const transition = await pickTransition(control, execution, transitions, execution) - if (transition === undefined) return res - res.push(...(await executeTransition(parent, transition, control, [], true))) - return res -} - -export async function OnExecutionTransition (txes: Tx[], control: TriggerControl): Promise { - const res: Tx[] = [] - for (const tx of txes) { - if (tx._class !== core.class.TxUpdateDoc) continue - const updateTx = tx as TxUpdateDoc - if (!control.hierarchy.isDerived(updateTx.objectClass, process.class.Execution)) continue - if (updateTx.operations.currentState === undefined) continue - const execution = ( - await control.findAll(control.ctx, process.class.Execution, { _id: updateTx.objectId }, { limit: 1 }) - )[0] - const cardUpdateTransitions = control.modelDb.findAllSync(process.class.Transition, { - from: execution.currentState, - process: execution.process, - trigger: process.trigger.OnCardUpdate - }) - if (cardUpdateTransitions.length > 0) { - const doc = (await control.findAll(control.ctx, cardPlugin.class.Card, { _id: execution.card }, { limit: 1 }))[0] - if (doc !== undefined) { - const transition = await pickTransition(control, execution, cardUpdateTransitions, doc) - if (transition !== undefined) { - res.push(...(await executeTransition(execution, transition, control, []))) - continue + await putEventToQueue( + { + event: process.trigger.OnToDoClose, + execution: todo.execution, + context: { + todo } - } - } else { - const subProcessesTransitions = control.modelDb.findAllSync(process.class.Transition, { - from: execution.currentState, - process: execution.process, - trigger: process.trigger.OnSubProcessesDone - }) - if (subProcessesTransitions.length > 0) { - const subProcesses = await control.findAll(control.ctx, process.class.Execution, { - parentId: execution._id, - status: ExecutionStatus.Active - }) - if (subProcesses.length === 0) { - const transition = await pickTransition(control, execution, subProcessesTransitions, execution) - if (transition !== undefined) { - res.push(...(await executeTransition(execution, transition, control, []))) - continue - } - } - } - } + }, + control + ) } return res } @@ -595,8 +118,14 @@ export async function OnExecutionCreate (txes: Tx[], control: TriggerControl): P const createTx = tx as TxCreateDoc if (!control.hierarchy.isDerived(createTx.objectClass, process.class.Execution)) continue const execution = TxProcessor.createDoc2Doc(createTx) - - res.push(...(await initState(execution, control))) + await putEventToQueue( + { + event: process.trigger.OnExecutionStart, + execution: execution._id, + context: {} + }, + control + ) } return res } @@ -609,221 +138,20 @@ export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl): if (!control.hierarchy.isDerived(removeTx.objectClass, process.class.ProcessToDo)) continue const removedTodo = control.removedMap.get(removeTx.objectId) as ProcessToDo if (removedTodo === undefined) continue - const execution = (await control.findAll(control.ctx, process.class.Execution, { _id: removedTodo.execution }))[0] - if (execution === undefined) continue - if (removedTodo.withRollback) { - const rollback = execution.rollback.pop() - if (rollback !== undefined) { - for (const rollbackTx of rollback) { - res.push(rollbackTx) + await putEventToQueue( + { + event: process.trigger.OnToDoRemove, + execution: removedTodo.execution, + context: { + todo: removedTodo } - res.push( - control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, { - execution: execution._id, - process: execution.process, - card: execution.card, - action: ExecutionLogAction.Rollback - }) - ) - } - } else { - const transitions = control.modelDb.findAllSync(process.class.Transition, { - from: execution.currentState, - process: execution.process, - trigger: process.trigger.OnToDoRemove - }) - const transition = await pickTransition(control, execution, transitions, removedTodo) - if (transition === undefined) continue - const rollback: Tx[] = [ - control.txFactory.createTxCreateDoc( - removedTodo._class, - removedTodo.space, - { - ...removedTodo - }, - removedTodo._id, - removedTodo.modifiedOn, - removeTx.modifiedBy - ) - ] - res.push(...(await executeTransition(execution, transition, control, rollback))) - } + }, + control + ) } return res } -export async function CreateToDo ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise { - for (const key in { user: params.user, title: params.title }) { - const val = (params as any)[key] - if (isEmpty(val)) { - throw processError(process.error.RequiredParamsNotProvided, { params: key }) - } - } - if (params.user === undefined || params.title === undefined) return - const res: Tx[] = [] - const rollback: Tx[] = [] - const id = generateId() - res.push( - control.txFactory.createTxCreateDoc( - process.class.ProcessToDo, - time.space.ToDos, - { - attachedTo: execution.card, - attachedToClass: cardPlugin.class.Card, - collection: 'todos', - workslots: 0, - execution: execution._id, - title: params.title, - user: params.user, - description: params.description ?? '', - dueDate: params.dueDate, - priority: params.priority ?? ToDoPriority.NoPriority, - visibility: 'public', - rank: '', - withRollback: params.withRollback ?? false - }, - id - ) - ) - return { txes: res, rollback, context: id } -} - -export async function CreateCard ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise { - const { _class, title, ...attrs } = params - for (const key in { _class, title }) { - const val = (params as any)[key] - if (isEmpty(val)) { - throw processError(process.error.RequiredParamsNotProvided, { params: key }) - } - } - const _id = generateId() - const data = { - title, - ...attrs - } as any - const res: Tx[] = [control.txFactory.createTxCreateDoc(_class as Ref, execution.space, data, _id)] - const rollback: Tx[] = [control.txFactory.createTxRemoveDoc(_class as Ref, execution.space, _id)] - return { txes: res, rollback, context: _id } -} - -export async function AddRelation ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise { - const _id = generateId() - const association = params.association as Ref - if (isEmpty(association)) { - throw processError(process.error.RequiredParamsNotProvided, { params: 'association' }) - } - if (isEmpty(params._id)) { - throw processError(process.error.RequiredParamsNotProvided, { params: '_id' }) - } - if (isEmpty(params.direction)) { - throw processError(process.error.RequiredParamsNotProvided, { params: 'direction' }) - } - const targetId = params._id as Ref - const direction = params.direction as 'A' | 'B' - const docA = direction === 'A' ? targetId : execution.card - const docB = direction === 'A' ? execution.card : targetId - const data: Data = { - association, - docA, - docB - } - const res: Tx[] = [control.txFactory.createTxCreateDoc(core.class.Relation, core.space.Workspace, data, _id)] - const rollback: Tx[] = [control.txFactory.createTxRemoveDoc(core.class.Relation, core.space.Workspace, _id)] - return { txes: res, rollback, context: _id } -} - -export async function UpdateCard ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise { - if (Object.keys(params).length === 0) return - const target = (await control.findAll(control.ctx, cardPlugin.class.Card, { _id: execution.card }, { limit: 1 }))[0] - if (target === undefined) return - const update: Record = {} - const prevValue: Record = {} - for (const key in params) { - prevValue[key] = (target as any)[key] - update[key] = (params as any)[key] - } - const res: Tx[] = [control.txFactory.createTxUpdateDoc(target._class, target.space, target._id, update)] - const rollback: Tx[] = [control.txFactory.createTxUpdateDoc(target._class, target.space, target._id, prevValue)] - return { txes: res, rollback, context: null } -} - -export async function RunSubProcess ( - params: MethodParams, - execution: Execution, - control: TriggerControl -): Promise { - if (params._id === undefined) return - const card = params.card ?? execution.card - const processId = params._id as Ref - const target = control.modelDb.findObject(processId) - if (target === undefined) return - const res: Tx[] = [] - const context: Ref[] = [] - for (const _card of Array.isArray(card) ? card : [card]) { - if (target.parallelExecutionForbidden === true) { - const currentExecution = await control.findAll(control.ctx, process.class.Execution, { - process: target._id, - card: _card, - done: false - }) - if (currentExecution.length > 0) { - // todo, show erro after merge another pr - continue - } - } - const initTransition = control.modelDb.findAllSync(process.class.Transition, { process: target._id, from: null })[0] - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const emptyContext = {} as ExecutionContext - const id = generateId() - res.push( - control.txFactory.createTxCreateDoc( - process.class.Execution, - core.space.Workspace, - { - process: processId, - currentState: initTransition.to, - card: _card, - context: emptyContext, - status: ExecutionStatus.Active, - rollback: [], - parentId: execution._id - }, - id - ) - ) - context.push(id) - } - return { txes: res, rollback: undefined, context } -} - -export async function RoleContext ( - value: null, - props: Record, - control: TriggerControl, - execution: Execution -): Promise[]> { - const targetRole = props.target - if (targetRole === undefined) return [] - const users = await control.findAll(control.ctx, contact.class.UserRole, { role: targetRole }) - return users.map((it) => it.user) -} - export async function OnExecutionContinue (txes: Tx[], control: TriggerControl): Promise { const res: Tx[] = [] for (const tx of txes) { @@ -837,13 +165,16 @@ export async function OnExecutionContinue (txes: Tx[], control: TriggerControl): if (execution === undefined) continue const error = execution.error if (error == null) continue - res.push(control.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { error: null })) - const _process = await control.modelDb.findOne(process.class.Process, { _id: execution.process }) - if (_process === undefined) continue - const transition = control.modelDb.findObject(error[0].transition) - if (transition !== undefined) { - res.push(...(await executeTransition(execution, transition, control, []))) - } + const transition = execution.error?.[0].transition + if (transition === undefined) continue + await putEventToQueue( + { + event: process.trigger.OnExecutionContinue, + execution: execution._id, + context: {} + }, + control + ) } return res } @@ -888,16 +219,6 @@ export async function OnStateRemove (txes: Tx[], control: TriggerControl): Promi return res } -export function CheckToDo (params: Record, doc: Doc): boolean { - if (params._id === undefined) return false - return doc._id === params._id -} - -export function OnCardUpdateCheck (params: Record, doc: Doc, hierarchy: Hierarchy): boolean { - const res = matchQuery([doc], params, doc._class, hierarchy, true) - return res.length > 0 -} - export async function OnTransition (txes: Tx[], control: TriggerControl): Promise { const res: Tx[] = [] for (const tx of txes) { @@ -923,23 +244,18 @@ export async function OnCardUpdate (txes: Tx[], control: TriggerControl): Promis if (!control.hierarchy.isDerived(tx._class, core.class.TxCUD)) continue const cudTx = tx as TxCUD if (!control.hierarchy.isDerived(cudTx.objectClass, cardPlugin.class.Card)) continue - const executions = await control.findAll(control.ctx, process.class.Execution, { - card: cudTx.objectId, - status: ExecutionStatus.Active - }) - if (executions.length === 0) continue const card = await control.findAll(control.ctx, cardPlugin.class.Card, { _id: cudTx.objectId }, { limit: 1 }) if (card.length === 0) continue - for (const execution of executions) { - const transitions = control.modelDb.findAllSync(process.class.Transition, { - from: execution.currentState, - process: execution.process, - trigger: process.trigger.OnCardUpdate - }) - const transition = await pickTransition(control, execution, transitions, card[0]) - if (transition === undefined) continue - res.push(...(await executeTransition(execution, transition, control, []))) - } + await putEventToQueue( + { + event: process.trigger.OnCardUpdate, + card: cudTx.objectId, + context: { + card: card[0] + } + }, + control + ) } return res } @@ -1009,10 +325,6 @@ async function syncContext (control: TriggerControl, _process: Process): Promise } } -function isEmpty (value: any): boolean { - return value === undefined || value === null || (typeof value === 'string' && value.trim() === '') -} - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ func: { @@ -1056,13 +368,16 @@ export default async () => ({ RemoveFirst, RemoveLast }, + rollbacks: { + ToDoCloseRollback, + ToDoCancellRollback + }, trigger: { OnProcessRemove, OnStateRemove, OnTransition, OnCardUpdate, OnExecutionCreate, - OnExecutionTransition, OnProcessToDoClose, OnProcessToDoRemove, OnExecutionContinue diff --git a/server-plugins/process-resources/src/rollback.ts b/server-plugins/process-resources/src/rollback.ts new file mode 100644 index 0000000000..925e200035 --- /dev/null +++ b/server-plugins/process-resources/src/rollback.ts @@ -0,0 +1,39 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Tx } from '@hcengineering/core' +import { ProcessToDo } from '@hcengineering/process' +import { ProcessControl } from '@hcengineering/server-process' + +export function ToDoCloseRollback (context: Record, control: ProcessControl): Tx | undefined { + const todo = context.todo as ProcessToDo + if (todo === undefined) return + return control.client.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { doneOn: null }) +} + +export function ToDoCancellRollback (context: Record, control: ProcessControl): Tx | undefined { + const todo = context.todo as ProcessToDo + if (todo === undefined) return + return control.client.txFactory.createTxCreateDoc( + todo._class, + todo.space, + { + ...todo + }, + todo._id, + todo.modifiedOn, + todo.modifiedBy + ) +} diff --git a/server-plugins/process-resources/src/transform.ts b/server-plugins/process-resources/src/transform.ts index 20702e6089..5b1af4d717 100644 --- a/server-plugins/process-resources/src/transform.ts +++ b/server-plugins/process-resources/src/transform.ts @@ -13,9 +13,10 @@ // limitations under the License. // -import { Doc, Timestamp } from '@hcengineering/core' +import contact, { Employee } from '@hcengineering/contact' +import { Doc, Ref, Timestamp } from '@hcengineering/core' import { Execution, parseContext } from '@hcengineering/process' -import { TriggerControl } from '@hcengineering/server-core' +import { ProcessControl } from '@hcengineering/server-process' import { getAttributeValue } from './utils' // #region ArrayReduce @@ -46,7 +47,7 @@ export function All (value: Doc[]): Doc[] { export async function Insert ( value: Doc[], props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { if (!Array.isArray(value)) return value @@ -54,7 +55,7 @@ export async function Insert ( const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const addition = await getAttributeValue(control, execution, context) + const addition = getAttributeValue(control, execution, context) value.push(addition) } } else { @@ -66,7 +67,7 @@ export async function Insert ( export async function Remove ( value: Doc[], props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { if (!Array.isArray(value)) return value @@ -74,7 +75,7 @@ export async function Remove ( const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const addition = await getAttributeValue(control, execution, context) + const addition = getAttributeValue(control, execution, context) return value.filter((item) => item !== addition) } } else { @@ -115,13 +116,13 @@ export function Trim (value: string): string { export async function Prepend ( value: string, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const addition = await getAttributeValue(control, execution, context) + const addition = getAttributeValue(control, execution, context) if (typeof addition !== 'string') return value return addition + value } @@ -134,13 +135,13 @@ export async function Prepend ( export async function Append ( value: string, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const addition = await getAttributeValue(control, execution, context) + const addition = getAttributeValue(control, execution, context) if (typeof addition !== 'string') return value return value + addition } @@ -210,13 +211,13 @@ export function Offset (val: Timestamp, props: Record): Timestamp { export async function Add ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const offset = await getAttributeValue(control, execution, context) + const offset = getAttributeValue(control, execution, context) return value + offset } } else if (typeof value === 'number' && typeof props.value === 'number') { @@ -228,13 +229,13 @@ export async function Add ( export async function Subtract ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const offset = await getAttributeValue(control, execution, context) + const offset = getAttributeValue(control, execution, context) return value - offset } } else if (typeof value === 'number' && typeof props.value === 'number') { @@ -246,13 +247,13 @@ export async function Subtract ( export async function Multiply ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const val = await getAttributeValue(control, execution, context) + const val = getAttributeValue(control, execution, context) return value * val } } else if (typeof value === 'number' && typeof props.value === 'number') { @@ -264,13 +265,13 @@ export async function Multiply ( export async function Divide ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const val = await getAttributeValue(control, execution, context) + const val = getAttributeValue(control, execution, context) if (val === 0) { return value // Avoid division by zero } @@ -288,13 +289,13 @@ export async function Divide ( export async function Modulo ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const val = await getAttributeValue(control, execution, context) + const val = getAttributeValue(control, execution, context) if (val === 0) { return value // Avoid division by zero } @@ -312,13 +313,13 @@ export async function Modulo ( export async function Power ( value: number, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ): Promise { const context = parseContext(props.value) if (context !== undefined) { if (context.type === 'attribute') { - const val = await getAttributeValue(control, execution, context) + const val = getAttributeValue(control, execution, context) return Math.pow(value, val) } } else if (typeof value === 'number' && typeof props.value === 'number') { @@ -356,3 +357,19 @@ export function Floor (value: number): number { } // #endregion + +// #region Func + +export async function RoleContext ( + value: null, + props: Record, + control: ProcessControl, + execution: Execution +): Promise[]> { + const targetRole = props.target + if (targetRole === undefined) return [] + const users = await control.client.findAll(contact.class.UserRole, { role: targetRole }) + return users.map((it) => it.user) +} + +// #endregion diff --git a/server-plugins/process-resources/src/utils.ts b/server-plugins/process-resources/src/utils.ts index 029eded8e4..6babf6f611 100644 --- a/server-plugins/process-resources/src/utils.ts +++ b/server-plugins/process-resources/src/utils.ts @@ -13,67 +13,22 @@ // limitations under the License. // -import { Doc, getObjectValue } from '@hcengineering/core' -import card from '@hcengineering/card' -import { getEmbeddedLabel, getResource } from '@hcengineering/platform' -import process, { Execution, SelectedContext, Transition, parseContext, processError } from '@hcengineering/process' -import { TriggerControl } from '@hcengineering/server-core' -import serverProcess from '@hcengineering/server-process' +import { getObjectValue } from '@hcengineering/core' +import { getEmbeddedLabel } from '@hcengineering/platform' +import process, { Execution, SelectedContext, processError } from '@hcengineering/process' +import { ProcessControl } from '@hcengineering/server-process' -export async function pickTransition ( - control: TriggerControl, - execution: Execution, - transitions: Transition[], - doc: Doc -): Promise { - for (const tr of transitions) { - const trigger = control.modelDb.findObject(tr.trigger) - if (trigger === undefined) continue - if (trigger.checkFunction === undefined) return tr - const impl = control.hierarchy.as(trigger, serverProcess.mixin.TriggerImpl) - if (impl?.serverCheckFunc === undefined) return tr - const filled = fillParams(tr.triggerParams, execution) - const checkFunc = await getResource(impl.serverCheckFunc) - if (checkFunc === undefined) continue - const res = await checkFunc(filled, doc, control.hierarchy) - if (res) return tr - } -} - -function fillParams (params: Record, execution: Execution): Record { - const res: Record = {} - for (const key in params) { - const value = params[key] - const context = parseContext(value) - if (context === undefined) { - res[key] = value - continue - } - if (context.type === 'context') { - res[key] = execution.context[context.id] - } - } - return res -} - -export async function getAttributeValue ( - control: TriggerControl, - execution: Execution, - context: SelectedContext -): Promise { - const cardValue = await control.findAll(control.ctx, card.class.Card, { _id: execution.card }, { limit: 1 }) - if (cardValue.length > 0) { - const val = getObjectValue(context.key, cardValue[0]) - if (val == null) { - const attr = control.hierarchy.findAttribute(cardValue[0]._class, context.key) - throw processError( - process.error.EmptyAttributeContextValue, - {}, - { attr: attr?.label ?? getEmbeddedLabel(context.key) } - ) - } - return val - } else { - throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true) +export function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any { + const card = control.cache.get(execution.card) + if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true) + const val = getObjectValue(context.key, card) + if (val == null) { + const attr = control.client.getHierarchy().findAttribute(card._class, context.key) + throw processError( + process.error.EmptyAttributeContextValue, + {}, + { attr: attr?.label ?? getEmbeddedLabel(context.key) } + ) } + return val } diff --git a/server-plugins/process/package.json b/server-plugins/process/package.json index 9a390336af..bc43403538 100644 --- a/server-plugins/process/package.json +++ b/server-plugins/process/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@hcengineering/core": "^0.6.32", + "@hcengineering/card": "^0.6.0", "@hcengineering/process": "^0.6.0", "@hcengineering/platform": "^0.6.11", "@hcengineering/server-core": "^0.6.1" diff --git a/server-plugins/process/src/index.ts b/server-plugins/process/src/index.ts index f9f032773f..5b4721a587 100644 --- a/server-plugins/process/src/index.ts +++ b/server-plugins/process/src/index.ts @@ -3,7 +3,7 @@ import type { Plugin, Resource } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' import { CheckFunc, Method, ProcessFunction, Trigger } from '@hcengineering/process' import { TriggerFunc } from '@hcengineering/server-core' -import { ExecuteFunc, TransformFunc } from './types' +import { ExecuteFunc, RollbackFunc, TransformFunc } from './types' export * from './types' @@ -14,6 +14,8 @@ export const serverProcessId = 'server-process' as Plugin export interface TriggerImpl extends Trigger { serverCheckFunc?: Resource + rollbackFunc?: Resource + preventRollback?: boolean } export interface MethodImpl extends Method { @@ -33,6 +35,10 @@ export default plugin(serverProcessId, { FuncImpl: '' as Ref>, TriggerImpl: '' as Ref> }, + rollbacks: { + ToDoCloseRollback: '' as Resource, + ToDoCancellRollback: '' as Resource + }, func: { RunSubProcess: '' as Resource, CreateToDo: '' as Resource, @@ -81,7 +87,6 @@ export default plugin(serverProcessId, { OnProcessRemove: '' as Resource, OnStateRemove: '' as Resource, OnExecutionCreate: '' as Resource, - OnExecutionTransition: '' as Resource, OnProcessToDoClose: '' as Resource, OnProcessToDoRemove: '' as Resource, OnExecutionContinue: '' as Resource diff --git a/server-plugins/process/src/types.ts b/server-plugins/process/src/types.ts index bc187bd1a4..ed46759de0 100644 --- a/server-plugins/process/src/types.ts +++ b/server-plugins/process/src/types.ts @@ -1,11 +1,11 @@ -import { Doc, Tx } from '@hcengineering/core' -import { Execution, ExecutionError, MethodParams } from '@hcengineering/process' -import { TriggerControl } from '@hcengineering/server-core' +import { Card } from '@hcengineering/card' +import { Doc, MeasureContext, PersonId, Ref, Tx, TxOperations } from '@hcengineering/core' +import { Execution, ExecutionError, MethodParams, Trigger } from '@hcengineering/process' export type ExecuteFunc = ( params: MethodParams, execution: Execution, - control: TriggerControl + control: ProcessControl ) => Promise export type ExecuteResult = SuccessExecutionResult | ExecutionError @@ -19,6 +19,23 @@ export interface SuccessExecutionResult { export type TransformFunc = ( value: any, props: Record, - control: TriggerControl, + control: ProcessControl, execution: Execution ) => Promise + +export interface ProcessMessage { + account: PersonId + event: Ref + context: Record + execution?: Ref + card?: Ref +} + +export interface ProcessControl { + ctx: MeasureContext + client: TxOperations + cache: Map + messageContext: Record +} + +export type RollbackFunc = (context: Record, control: ProcessControl) => Tx diff --git a/server/core/src/queue/types.ts b/server/core/src/queue/types.ts index c001fbae50..1c8693bd15 100644 --- a/server/core/src/queue/types.ts +++ b/server/core/src/queue/types.ts @@ -16,7 +16,10 @@ export enum QueueTopic { TelegramBot = 'telegramBot', // A topic about calendar events. - CalendarEventCUD = 'calendarEventCUD' + CalendarEventCUD = 'calendarEventCUD', + + // A topic about process events + Process = 'process' } export interface ConsumerHandle { diff --git a/services/process/.eslintrc.js b/services/process/.eslintrc.js new file mode 100644 index 0000000000..72235dc283 --- /dev/null +++ b/services/process/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/services/process/.npmignore b/services/process/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/services/process/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/services/process/Dockerfile b/services/process/Dockerfile new file mode 100644 index 0000000000..250a1e2da9 --- /dev/null +++ b/services/process/Dockerfile @@ -0,0 +1,7 @@ + +FROM hardcoreeng/base:v20250310 +WORKDIR /usr/src/app + +COPY bundle/bundle.js ./ + +CMD [ "node", "bundle.js" ] diff --git a/services/process/config/rig.json b/services/process/config/rig.json new file mode 100644 index 0000000000..0110930f55 --- /dev/null +++ b/services/process/config/rig.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig" +} diff --git a/services/process/jest.config.js b/services/process/jest.config.js new file mode 100644 index 0000000000..2cfd408b67 --- /dev/null +++ b/services/process/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + roots: ["./src"], + coverageReporters: ["text-summary", "html"] +} diff --git a/services/process/package.json b/services/process/package.json new file mode 100644 index 0000000000..d4183eff7b --- /dev/null +++ b/services/process/package.json @@ -0,0 +1,70 @@ +{ + "name": "@hcengineering/pod-process", + "version": "0.6.0", + "main": "lib/index.js", + "svelte": "src/index.ts", + "types": "types/index.d.ts", + "files": [ + "lib/**/*", + "types/**/*", + "tsconfig.json" + ], + "author": "Hardcore Engineering Inc.", + "scripts": { + "build": "compile", + "build:watch": "compile", + "test": "jest --passWithNoTests --silent", + "_phase:bundle": "rushx bundle", + "_phase:docker-build": "rushx docker:build", + "_phase:docker-staging": "rushx docker:staging", + "bundle": "node ../../common/scripts/esbuild.js", + "docker:build": "../../common/scripts/docker_build.sh hardcoreeng/process .", + "docker:tbuild": "docker build -t hardcoreeng/process . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/process", + "docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/process staging", + "docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/process", + "run-local": "cross-env ts-node src/index.ts", + "format": "format src", + "_phase:build": "compile transpile src", + "_phase:test": "jest --passWithNoTests --silent", + "_phase:format": "format src", + "_phase:validate": "compile validate" + }, + "devDependencies": { + "@tsconfig/node16": "^1.0.4", + "@types/node": "^22.15.29", + "@types/ws": "^8.5.11", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@hcengineering/platform-rig": "^0.6.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "@types/jest": "^29.5.5", + "@types/uuid": "^8.3.1", + "@typescript-eslint/parser": "^6.11.0", + "esbuild": "^0.24.2", + "eslint": "^8.54.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.4.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "prettier": "^3.1.0", + "ts-node": "^10.8.0", + "typescript": "^5.8.3" + }, + "dependencies": { + "@hcengineering/analytics": "^0.6.0", + "@hcengineering/analytics-service": "^0.6.0", + "@hcengineering/api-client": "^0.6.0", + "@hcengineering/core": "^0.6.32", + "@hcengineering/kafka": "^0.6.0", + "@hcengineering/platform": "^0.6.11", + "@hcengineering/card": "^0.6.0", + "@hcengineering/process": "^0.6.0", + "@hcengineering/server-process": "^0.6.0", + "@hcengineering/server-process-resources": "^0.6.0", + "@hcengineering/server-core": "^0.6.1", + "@hcengineering/server-token": "^0.6.11", + "@hcengineering/account-client": "^0.6.0", + "dotenv": "~16.0.0" + } +} diff --git a/services/process/src/config.ts b/services/process/src/config.ts new file mode 100644 index 0000000000..f3c3730808 --- /dev/null +++ b/services/process/src/config.ts @@ -0,0 +1,33 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +import { config as dotenvConfig } from 'dotenv' + +dotenvConfig() + +export interface Config { + Secret: string + QueueConfig: string + QueueRegion: string + AccountsUrl: string +} + +const config: Config = { + Secret: process.env.SECRET ?? 'secret', + QueueConfig: process.env.QUEUE_CONFIG ?? '', + QueueRegion: process.env.QUEUE_REGION ?? '', + AccountsUrl: process.env.ACCOUNTS_URL ?? '' +} + +export default config diff --git a/server-plugins/process-resources/src/errors.ts b/services/process/src/errors.ts similarity index 100% rename from server-plugins/process-resources/src/errors.ts rename to services/process/src/errors.ts diff --git a/services/process/src/index.ts b/services/process/src/index.ts new file mode 100644 index 0000000000..2b319bed8d --- /dev/null +++ b/services/process/src/index.ts @@ -0,0 +1,86 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { Analytics } from '@hcengineering/analytics' +import { configureAnalytics, createOpenTelemetryMetricsContext, SplitLogger } from '@hcengineering/analytics-service' +import { newMetrics } from '@hcengineering/core' +import { getPlatformQueue } from '@hcengineering/kafka' +import { setMetadata } from '@hcengineering/platform' +import { initStatisticsContext, QueueTopic } from '@hcengineering/server-core' +import { join } from 'path' +import serverToken from '@hcengineering/server-token' +import config from './config' +import { ProcessMessage } from '@hcengineering/server-process' +import { SERVICE_NAME } from './utils' +import { messageHandler } from './main' +import { prepare } from './init' + +async function main (): Promise { + prepare() + configureAnalytics(SERVICE_NAME, process.env.VERSION ?? '0.7.0') + const ctx = initStatisticsContext(SERVICE_NAME, { + factory: () => + createOpenTelemetryMetricsContext( + SERVICE_NAME, + {}, + {}, + newMetrics(), + new SplitLogger(SERVICE_NAME, { + root: join(process.cwd(), 'logs'), + enableConsole: (process.env.ENABLE_CONSOLE ?? 'true') === 'true' + }) + ) + }) + + Analytics.setTag('application', SERVICE_NAME) + setMetadata(serverToken.metadata.Secret, config.Secret) + setMetadata(serverToken.metadata.Service, SERVICE_NAME) + + const queue = getPlatformQueue(SERVICE_NAME, config.QueueRegion) + + const consumer = queue.createConsumer( + ctx, + QueueTopic.Process, + queue.getClientId(), + async (messages) => { + for (const message of messages) { + const ws = message.workspace + const records = message.value + for (const record of records) { + void messageHandler(record, ws, ctx) + } + } + } + ) + + const shutdown = (): void => { + void Promise.all([consumer.close()]).then(() => { + process.exit() + }) + } + + process.once('SIGINT', shutdown) + process.once('SIGTERM', shutdown) + process.on('uncaughtException', (error: any) => { + ctx.error('Uncaught exception', { error }) + }) + process.on('unhandledRejection', (error: any) => { + ctx.error('Unhandled rejection', { error }) + }) +} + +void main().catch((err) => { + console.error(err) +}) diff --git a/services/process/src/init.ts b/services/process/src/init.ts new file mode 100644 index 0000000000..da20b20bde --- /dev/null +++ b/services/process/src/init.ts @@ -0,0 +1,21 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { addLocation } from '@hcengineering/platform' +import { serverProcessId } from '@hcengineering/server-process' + +export function prepare (): void { + addLocation(serverProcessId, () => import('@hcengineering/server-process-resources')) +} diff --git a/services/process/src/main.ts b/services/process/src/main.ts new file mode 100644 index 0000000000..bd26a9c318 --- /dev/null +++ b/services/process/src/main.ts @@ -0,0 +1,612 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import cardPlugin from '@hcengineering/card' +import core, { + ArrOf, + Doc, + generateId, + getDiffUpdate, + getObjectValue, + MeasureContext, + Ref, + RefTo, + Tx, + TxProcessor, + WorkspaceUuid +} from '@hcengineering/core' +import { getEmbeddedLabel, getResource } from '@hcengineering/platform' +import process, { + Execution, + ExecutionError, + ExecutionLogAction, + ExecutionStatus, + MethodParams, + parseContext, + parseError, + processError, + ProcessError, + SelectedContext, + SelectedContextFunc, + SelectedExecutonContext, + SelectedNested, + SelectedRelation, + SelectedUserRequest, + State, + Step, + Transition +} from '@hcengineering/process' +import serverProcess, { + ExecuteResult, + MethodImpl, + ProcessControl, + ProcessMessage, + TriggerImpl +} from '@hcengineering/server-process' +import { isError } from './errors' +import { getClient, pickTransition, releaseClient } from './utils' + +const activeExecutions = new Set>() + +export async function messageHandler (record: ProcessMessage, ws: WorkspaceUuid, ctx: MeasureContext): Promise { + try { + const client = await getClient(ws, record.account) + try { + const control = { + ctx, + client, + cache: new Map(), + messageContext: record.context + } + if (record.execution !== undefined) { + const execution = await control.client.findOne(process.class.Execution, { _id: record.execution }) + if (execution !== undefined) { + await processExecution(control, record, execution) + } + } else if (record.card !== undefined) { + await processCardExecutions(control, record) + } else { + await processBroadcast(control, record) + } + } finally { + await releaseClient(ws, record.account) + } + } catch (error) { + ctx.error('Error processing event', { error, ws, record }) + } +} + +async function processBroadcast (control: ProcessControl, record: ProcessMessage): Promise { + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + trigger: record.event + }) + for (const transition of transitions) { + if (transition.from == null) continue + const executions = await control.client.findAll(process.class.Execution, { + process: transition.process, + currentState: transition.from + }) + for (const execution of executions) { + await execute(execution, transition, control) + } + } +} + +async function processCardExecutions (control: ProcessControl, record: ProcessMessage): Promise { + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + trigger: record.event + }) + const states = new Set>() + for (const transition of transitions) { + if (transition.from !== null) { + states.add(transition.from) + } + } + if (states.size === 0) return + const executions = await control.client.findAll(process.class.Execution, { + card: record.card, + status: ExecutionStatus.Active, + currentState: { $in: Array.from(states) } + }) + for (const execution of executions) { + await processExecution(control, record, execution) + } +} + +async function findTransitions ( + control: ProcessControl, + record: ProcessMessage, + execution: Execution +): Promise { + if (record.event === process.trigger.OnExecutionStart) { + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + process: execution.process, + trigger: record.event + }) + return await pickTransition(control, execution, transitions, record.context) + } + if (record.event === process.trigger.OnExecutionContinue) { + const transition = execution.error?.[0].transition + if (transition === undefined) return + return control.client.getModel().findAllSync(process.class.Transition, { + _id: transition, + from: execution.currentState, + process: execution.process + })[0] + } + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + from: execution.currentState, + process: execution.process, + trigger: record.event + }) + return await pickTransition(control, execution, transitions, record.context) +} + +async function processExecution (control: ProcessControl, record: ProcessMessage, execution: Execution): Promise { + const transition = await findTransitions(control, record, execution) + if (transition !== undefined) { + await execute(execution, transition, control) + } +} + +async function getTriggerRollback (triggger: TriggerImpl, control: ProcessControl): Promise { + if (triggger.rollbackFunc !== undefined) { + const rollbackFunc = await getResource(triggger.rollbackFunc) + if (rollbackFunc !== undefined) { + try { + return rollbackFunc(control.messageContext, control) + } catch (err: any) { + control.ctx.error('Error executing rollback function', { error: err, trigger: triggger._id }) + } + } + } +} + +async function execute (execution: Execution, transition: Transition, control: ProcessControl): Promise { + if (activeExecutions.has(execution._id)) { + control.ctx.info('Execution already in progress', { execution: execution._id, transition: transition._id }) + return + } + activeExecutions.add(execution._id) + try { + await executeTransition(execution, transition, control) + } catch (err: any) { + control.ctx.error('Error executing transition', { + error: err, + execution: execution._id, + transition: transition._id + }) + } finally { + activeExecutions.delete(execution._id) + } +} + +async function executeTransition (execution: Execution, transition: Transition, control: ProcessControl): Promise { + let deep = control.cache.get(execution._id + 'transition') ?? 0 + deep++ + control.cache.set(execution._id + 'transition', deep) + if (deep > 100) { + const error = parseError( + processError(process.error.TooDeepTransitionRecursion, undefined, undefined, true), + transition._id + ) + await control.client.update(execution, { error: [error] }) + return + } + const trigger = control.client.getModel().findObject(transition.trigger) + if (trigger === undefined) return + const rollback: Tx[] = [] + const triggerImpl = control.client.getHierarchy().as(trigger, serverProcess.mixin.TriggerImpl) + const disableRollback = triggerImpl?.preventRollback ?? false + const triggerRollback = await getTriggerRollback(triggerImpl, control) + if (triggerRollback !== undefined) { + rollback.push(triggerRollback) + } + const client = control.client + const res: Tx[] = [] + const _process = client.getModel().findObject(execution.process) + if (_process === undefined) return + + const state = client.getModel().findObject(transition.to) + if (state === undefined) return + const isDone = + client.getModel().findAllSync(process.class.Transition, { + from: transition.to, + process: transition.process + }).length === 0 + const errors: ExecutionError[] = [] + if (execution.currentState !== null) { + rollback.push( + client.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, { + currentState: execution.currentState, + status: execution.status + }) + ) + } else { + rollback.push(client.txFactory.createTxRemoveDoc(execution._class, execution.space, execution._id)) + } + if (!control.cache.has(execution.card)) { + const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + if (card !== undefined) { + control.cache.set(execution.card, card) + } + } + for (const action of transition.actions) { + const actionResult = await executeAction(action, transition._id, execution, control) + if (isError(actionResult)) { + errors.push(actionResult) + } else { + if (actionResult.rollback !== undefined) { + rollback.push(...actionResult.rollback) + } + res.push(...actionResult.txes) + } + } + if (!disableRollback) { + execution.rollback.push(rollback) + } + const executionUpdate = getDiffUpdate(execution, { + rollback: execution.rollback.length > 30 ? execution.rollback.slice(-30) : execution.rollback, + context: execution.context, + currentState: state._id, + status: isDone ? ExecutionStatus.Done : ExecutionStatus.Active, + error: null + }) + res.push(client.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, executionUpdate)) + res.push( + client.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, { + execution: execution._id, + process: execution.process, + card: execution.card, + transition: transition._id, + action: transition.from === null ? ExecutionLogAction.Started : ExecutionLogAction.Transition + }) + ) + if (errors.length === 0) { + for (const tx of res) { + const timeout = setTimeout(() => { + control.ctx.warn('TX HANG', tx) + }, 30000) + await client.tx(tx) + clearTimeout(timeout) + } + if (isDone && execution.parentId !== undefined) { + await checkParent(execution, control) + } + TxProcessor.applyUpdate(execution, executionUpdate) + await checkNext(control, execution) + } else { + await client.update(execution, { error: errors }) + } +} + +async function executeAction ( + action: Step, + transition: Ref, + execution: Execution, + control: ProcessControl +): Promise { + try { + const method = control.client.getModel().findObject(action.methodId) + if (method === undefined) throw processError(process.error.MethodNotFound, { methodId: action.methodId }, {}, true) + const impl = control.client.getHierarchy().as(method, serverProcess.mixin.MethodImpl) as MethodImpl + if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: action.methodId }, {}, true) + const params = await fillParams(action.params, execution, control) + const f = await getResource(impl.func) + const res = await f(params, execution, control) + if (!isError(res) && action.context?._id != null && res.context != null) { + execution.context[action.context._id] = res.context + } + return res + } catch (err) { + if (err instanceof ProcessError) { + if (err.shouldLog) { + control.ctx.error(err.message, { props: err.props }) + } + return parseError(err, transition) + } else { + const errorId = generateId() + control.ctx.error(err instanceof Error ? err.message : String(err), { errorId }) + return parseError(processError(process.error.InternalServerError, { errorId }), transition) + } + } +} + +async function fillValue ( + value: any, + context: SelectedContext, + control: ProcessControl, + execution: Execution +): Promise { + for (const func of context.functions ?? []) { + const transform = control.client.getModel().findObject(func.func) + if (transform === undefined) throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true) + if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) { + throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true) + } + const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl) + const f = await getResource(funcImpl.func) + value = await f(value, func.props, control, execution) + } + return value +} + +async function getNestedValue ( + control: ProcessControl, + execution: Execution, + context: SelectedNested +): Promise { + const card = control.cache.get(execution.card) + if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true) + const attr = control.client.getHierarchy().findAttribute(card._class, context.path) + if (attr === undefined) throw processError(process.error.AttributeNotExists, { key: context.path }) + const nestedValue = getObjectValue(context.path, card) + if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label }) + const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf).of : attr.type + const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo).to : parentType._class + const target = await control.client.findAll(targetClass, { + _id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] } + }) + if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label }) + const nested = control.client.getHierarchy().findAttribute(targetClass, context.key) + if (context.sourceFunction !== undefined) { + const transform = control.client.getModel().findObject(context.sourceFunction) + if (transform === undefined) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl) + const f = await getResource(funcImpl.func) + const reduced = await f(target, {}, control, execution) + const val = getObjectValue(context.key, reduced) + if (val == null) { + throw processError( + process.error.EmptyRelatedObjectValue, + {}, + { parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) } + ) + } + return val + } + const val = getObjectValue(context.key, target[0]) + if (val == null) { + throw processError( + process.error.EmptyRelatedObjectValue, + {}, + { parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) } + ) + } + return val +} + +async function getRelationValue ( + control: ProcessControl, + execution: Execution, + context: SelectedRelation +): Promise { + const assoc = control.client.getModel().findObject(context.association) + if (assoc === undefined) throw processError(process.error.RelationNotExists, {}) + const targetClass = context.direction === 'A' ? assoc.classA : assoc.classB + const q = context.direction === 'A' ? { docB: execution.card } : { docA: execution.card } + const relations = await control.client.findAll(core.class.Relation, { association: assoc._id, ...q }) + const name = context.direction === 'A' ? assoc.nameA : assoc.nameB + if (relations.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: name }) + const ids = relations.map((it) => { + return context.direction === 'A' ? it.docA : it.docB + }) + const target = await control.client.findAll(targetClass, { _id: { $in: ids } }) + if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name }) + const attr = context.key !== '' ? control.client.getHierarchy().findAttribute(targetClass, context.key) : undefined + if (context.sourceFunction !== undefined) { + const transform = control.client.getModel().findObject(context.sourceFunction) + if (transform === undefined) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl) + const f = await getResource(funcImpl.func) + const reduced = await f(target, {}, control, execution) + const val = Array.isArray(reduced) + ? reduced.map((v) => getObjectValue(context.key, v)) + : getObjectValue(context.key, reduced) + if (val == null) { + throw processError( + process.error.EmptyRelatedObjectValue, + { parent: name }, + { attr: attr?.label ?? getEmbeddedLabel(context.name) } + ) + } + return val + } + const val = + Array.isArray(target) && target.length > 1 + ? target.map((v) => getObjectValue(context.key, v)) + : getObjectValue(context.key, target[0]) + if (val == null) { + throw processError( + process.error.EmptyRelatedObjectValue, + { parent: name }, + { attr: attr?.label ?? getEmbeddedLabel(context.name) } + ) + } + return val +} + +async function fillParams ( + params: MethodParams, + execution: Execution, + control: ProcessControl +): Promise> { + const res: MethodParams = {} + for (const key in params) { + const value = (params as any)[key] + const valueResult = await getContextValue(value, control, execution) + ;(res as any)[key] = valueResult + } + return res +} + +function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any { + const card = control.cache.get(execution.card) + if (card !== undefined) { + const val = getObjectValue(context.key, card) + if (val == null) { + const attr = control.client.getHierarchy().findAttribute(card._class, context.key) + throw processError( + process.error.EmptyAttributeContextValue, + {}, + { attr: attr?.label ?? getEmbeddedLabel(context.key) } + ) + } + return val + } else { + throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true) + } +} + +async function getContextValue (value: any, control: ProcessControl, execution: Execution): Promise { + const context = parseContext(value) + if (context !== undefined) { + let value: any | undefined + try { + if (context.type === 'attribute') { + value = await getAttributeValue(control, execution, context) + } else if (context.type === 'relation') { + value = await getRelationValue(control, execution, context) + } else if (context.type === 'nested') { + value = await getNestedValue(control, execution, context) + } else if (context.type === 'userRequest') { + value = getUserRequestValue(control, execution, context) + } else if (context.type === 'function') { + value = await getFunctionValue(control, execution, context) + } else if (context.type === 'context') { + value = getExecutionContextValue(control, execution, context) + } + return await fillValue(value, context, control, execution) + } catch (err: any) { + if (err instanceof ProcessError && context.fallbackValue !== undefined) { + return await fillValue(context.fallbackValue, context, control, execution) + } + throw err + } + } else { + return value + } +} + +async function getFunctionValue ( + control: ProcessControl, + execution: Execution, + context: SelectedContextFunc +): Promise { + const func = control.client.getModel().findObject(context.func) + if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true) + const impl = control.client.getHierarchy().as(func, serverProcess.mixin.FuncImpl) + if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true) + const f = await getResource(impl.func) + const res = await f(null, context.props, control, execution) + if (context.sourceFunction !== undefined) { + const transform = control.client.getModel().findObject(context.sourceFunction) + if (transform === undefined) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) { + throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true) + } + const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl) + const f = await getResource(funcImpl.func) + const val = await f(res, {}, control, execution) + if (val == null) { + throw processError(process.error.EmptyFunctionResult, {}, { func: func.label }) + } + return val + } + if (res == null) { + throw processError(process.error.EmptyFunctionResult, {}, { func: func.label }) + } + return res +} + +function getUserRequestValue (control: ProcessControl, execution: Execution, context: SelectedUserRequest): any { + const userContext = execution.context[context.id] + if (userContext !== undefined) return userContext + const attr = control.client.getHierarchy().findAttribute(context._class, context.key) + throw processError( + process.error.UserRequestedValueNotProvided, + {}, + { attr: attr?.label ?? getEmbeddedLabel(context.key) } + ) +} + +function getExecutionContextValue ( + control: ProcessControl, + execution: Execution, + context: SelectedExecutonContext +): any { + const userContext = execution.context[context.id] + if (userContext !== undefined) return userContext + const _process = control.client.getModel().findObject(execution.process) + if (_process === undefined) return + const ctx = _process.context[context.id] + if (ctx === undefined) return + throw processError(process.error.ContextValueNotProvided, { name: ctx.name }) +} + +async function checkParent (execution: Execution, control: ProcessControl): Promise { + const subProcesses = await control.client.findAll(process.class.Execution, { + parentId: execution.parentId, + status: ExecutionStatus.Active + }) + const filtered = subProcesses.filter((it) => it._id !== execution._id) + if (filtered.length !== 0) return + const parent = (await control.client.findAll(process.class.Execution, { _id: execution.parentId }))[0] + if (parent === undefined) return + const _process = control.client.getModel().findObject(parent.process) + if (_process === undefined) return + if (parent.status !== ExecutionStatus.Active) return + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + from: parent.currentState, + process: parent.process, + trigger: process.trigger.OnSubProcessesDone + }) + const transition = await pickTransition(control, execution, transitions, {}) + if (transition === undefined) return + await executeTransition(parent, transition, control) +} + +async function checkNext (control: ProcessControl, execution: Execution): Promise { + const autoTriggers = control.client.getModel().findAllSync(process.class.Trigger, { auto: true }) + const transitions = control.client.getModel().findAllSync(process.class.Transition, { + from: execution.currentState, + process: execution.process, + trigger: { $in: autoTriggers.map((it) => it._id) } + }) + if (transitions.length > 0) { + const doc = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }) + if (doc !== undefined) { + const transition = await pickTransition(control, execution, transitions, { + card: doc + }) + if (transition !== undefined) { + control.cache.set(execution.card, doc) + await executeTransition(execution, transition, control) + } + } + } +} diff --git a/services/process/src/utils.ts b/services/process/src/utils.ts new file mode 100644 index 0000000000..ab5df359b1 --- /dev/null +++ b/services/process/src/utils.ts @@ -0,0 +1,139 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { getClient as getAccountClient } from '@hcengineering/account-client' +import { createRestTxOperations } from '@hcengineering/api-client' +import core, { PersonId, systemAccountUuid, TxOperations, WorkspaceUuid } from '@hcengineering/core' +import { getResource } from '@hcengineering/platform' +import { Execution, parseContext, Transition } from '@hcengineering/process' +import serverProcess, { ProcessControl } from '@hcengineering/server-process' +import { generateToken } from '@hcengineering/server-token' +import config from './config' + +export const SERVICE_NAME = 'process-service' + +const clients = new Map>() +const clientUsage = new Map() + +export async function getClient (workspaceUuid: WorkspaceUuid, socialId?: PersonId): Promise { + const key = `${workspaceUuid}-${socialId ?? ''}` + let usage = clientUsage.get(key) ?? 0 + usage++ + clientUsage.set(key, usage) + const current = clients.get(key) + if (current !== undefined) { + if (current instanceof Promise) { + return await current + } + return current + } + + const client = createClient(workspaceUuid, socialId) + clients.set(key, client) + const cl = await client + clients.set(key, cl) + return cl +} + +export async function releaseClient (workspaceUuid: WorkspaceUuid, socialId?: PersonId): Promise { + const key = `${workspaceUuid}-${socialId ?? ''}` + let usage = clientUsage.get(key) + if (usage === undefined) { + console.warn(`Client for ${key} not found`) + return + } + usage-- + clientUsage.set(key, usage) + if (usage <= 0) { + setTimeout(() => { + close(key).catch((err) => { + console.error(`Failed to close client for ${key}`, err) + }) + }, 60000) + } +} + +async function close (key: string): Promise { + const usage = clientUsage.get(key) ?? 0 + if (usage > 0) return + const current = clients.get(key) + if (current !== undefined) { + clients.delete(key) + if (current instanceof Promise) { + const resolvedClient = await current + await resolvedClient.close() + } else { + await current.close() + } + } +} + +async function createClient (workspaceUuid: WorkspaceUuid, socialId?: PersonId): Promise { + const token = generateToken(systemAccountUuid, workspaceUuid, { service: SERVICE_NAME }) + let accountClient = getAccountClient(config.AccountsUrl, token) + + if (socialId !== undefined && socialId !== core.account.System) { + const personUuid = await accountClient.findPersonBySocialId(socialId, true) + if (personUuid === undefined) { + throw new Error('Global person not found') + } + const token = generateToken(personUuid, workspaceUuid, { service: SERVICE_NAME }) + accountClient = getAccountClient(config.AccountsUrl, token) + } + + const wsInfo = await accountClient.getLoginInfoByToken() + if (!('endpoint' in wsInfo)) { + throw new Error('Invalid login info') + } + const transactorUrl = wsInfo.endpoint.replace('ws://', 'http://').replace('wss://', 'https://') + const client = await createRestTxOperations(transactorUrl, wsInfo.workspace, wsInfo.token, true) + return client +} + +export async function pickTransition ( + control: ProcessControl, + execution: Execution, + transitions: Transition[], + context: Record +): Promise { + for (const tr of transitions) { + const trigger = control.client.getModel().findObject(tr.trigger) + if (trigger === undefined) continue + if (trigger.checkFunction === undefined) return tr + const impl = control.client.getHierarchy().as(trigger, serverProcess.mixin.TriggerImpl) + if (impl?.serverCheckFunc === undefined) return tr + const filled = fillParams(tr.triggerParams, execution) + const checkFunc = await getResource(impl.serverCheckFunc) + if (checkFunc === undefined) continue + const res = await checkFunc(filled, context, control.client.getHierarchy()) + if (res) return tr + } +} + +function fillParams (params: Record, execution: Execution): Record { + const res: Record = {} + for (const key in params) { + const value = params[key] + const context = parseContext(value) + if (context === undefined) { + res[key] = value + continue + } + if (context.type === 'context') { + res[key] = execution.context[context.id] + } + } + return res +} diff --git a/services/process/tsconfig.json b/services/process/tsconfig.json new file mode 100644 index 0000000000..b5ae22f6e4 --- /dev/null +++ b/services/process/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types", + "tsBuildInfoFile": ".build/build.tsbuildinfo" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "lib", "dist", "types", "bundle"] +} \ No newline at end of file