@friggframework/core 2.0.0-next.12 → 2.0.0-next.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +18 -1
- package/CLAUDE.md +830 -0
- package/README.md +996 -50
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
- package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
- package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/README.md +451 -0
- package/application/commands/admin-script-commands.js +359 -0
- package/application/commands/credential-commands.js +262 -0
- package/application/commands/entity-commands.js +294 -0
- package/application/commands/integration-commands.js +419 -0
- package/application/commands/integration-mapping-commands.js +25 -0
- package/application/commands/process-commands.js +135 -0
- package/application/commands/report-commands.js +188 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/usage-commands.js +56 -0
- package/application/commands/user-commands.js +312 -0
- package/application/index.js +83 -0
- package/artifacts/repositories/artifact-repository-factory.js +19 -0
- package/artifacts/repositories/artifact-repository-interface.js +27 -0
- package/artifacts/repositories/artifact-repository-local.js +42 -0
- package/artifacts/repositories/artifact-repository-s3.js +61 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +715 -0
- package/core/Worker.js +55 -24
- package/core/create-handler.js +121 -38
- package/core/index.js +8 -1
- package/core/invocation-scope.js +225 -0
- package/core/parameters-to-env.js +257 -0
- package/core/secrets-to-env.js +7 -6
- package/core/ssm-preload.mjs +34 -0
- package/credential/repositories/credential-active-type.js +32 -0
- package/credential/repositories/credential-repository-documentdb.js +355 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +113 -0
- package/credential/repositories/credential-repository-mongo.js +294 -0
- package/credential/repositories/credential-repository-postgres.js +312 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +147 -0
- package/database/documentdb-encryption-service.js +334 -0
- package/database/documentdb-utils.js +196 -0
- package/database/encryption/README.md +863 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +404 -0
- package/database/encryption/field-encryption-service.js +254 -0
- package/database/encryption/integration-mapping-encryption.js +97 -0
- package/database/encryption/logger.js +82 -0
- package/database/encryption/prisma-encryption-extension.js +241 -0
- package/database/index.js +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +491 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +59 -57
- package/errors/index.js +6 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +5 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +5 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +386 -0
- package/generated/prisma-mongodb/index-browser.js +368 -0
- package/generated/prisma-mongodb/index.d.ts +27065 -0
- package/generated/prisma-mongodb/index.js +411 -0
- package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
- package/generated/prisma-mongodb/runtime/edge.js +35 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
- package/generated/prisma-mongodb/runtime/library.js +147 -0
- package/generated/prisma-mongodb/runtime/react-native.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-mongodb/schema.prisma +455 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +393 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +5 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +5 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +407 -0
- package/generated/prisma-postgresql/index-browser.js +389 -0
- package/generated/prisma-postgresql/index.d.ts +29904 -0
- package/generated/prisma-postgresql/index.js +432 -0
- package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
- package/generated/prisma-postgresql/runtime/edge.js +35 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
- package/generated/prisma-postgresql/runtime/library.js +147 -0
- package/generated/prisma-postgresql/runtime/react-native.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-postgresql/schema.prisma +436 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +414 -0
- package/handlers/WEBHOOKS.md +682 -0
- package/handlers/app-definition-loader.js +103 -0
- package/handlers/app-handler-helpers.js +20 -6
- package/handlers/backend-utils.js +272 -46
- package/handlers/database-migration-handler.js +236 -0
- package/handlers/integration-event-dispatcher.js +68 -0
- package/handlers/middleware/admin-auth.js +73 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +4 -15
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +329 -0
- package/handlers/routers/health.js +518 -0
- package/handlers/routers/integration-defined-routers.js +85 -10
- package/handlers/routers/integration-webhook-routers.js +87 -0
- package/handlers/routers/user.js +27 -5
- package/handlers/routers/websocket.js +31 -9
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +438 -0
- package/handlers/workers/dlq-processor.js +89 -0
- package/handlers/workers/integration-defined-workers.js +13 -7
- package/index.js +123 -47
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +238 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/EXTENSIONS.md +240 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/extension.js +254 -0
- package/integrations/index.js +20 -10
- package/integrations/integration-base.js +777 -74
- package/integrations/integration-router.js +399 -180
- package/integrations/options.js +1 -1
- package/integrations/repositories/config-patch-shared.js +43 -0
- package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
- package/integrations/repositories/integration-mapping-query.js +193 -0
- package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
- package/integrations/repositories/integration-mapping-repository.js +185 -0
- package/integrations/repositories/integration-repository-documentdb.js +354 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +219 -0
- package/integrations/repositories/integration-repository-mongo.js +502 -0
- package/integrations/repositories/integration-repository-postgres.js +550 -0
- package/integrations/repositories/process-repository-documentdb.js +311 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +136 -0
- package/integrations/repositories/process-repository-mongo.js +262 -0
- package/integrations/repositories/process-repository-postgres.js +380 -0
- package/integrations/repositories/process-update-ops-shared.js +112 -0
- package/integrations/repositories/report-id.js +13 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +113 -0
- package/integrations/tests/doubles/test-integration-repository.js +146 -0
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +253 -0
- package/integrations/use-cases/create-process.js +130 -0
- package/integrations/use-cases/delete-integration-for-user.js +130 -0
- package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +89 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/patch-integration-config.js +39 -0
- package/integrations/use-cases/process-errors.js +28 -0
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/use-cases/update-integration-config.js +32 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +222 -0
- package/integrations/use-cases/update-process-state.js +163 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/jest.config.js +2 -0
- package/logs/.eslintrc.json +26 -1
- package/logs/__fixtures__/events.js +82 -0
- package/logs/__fixtures__/matchers.js +45 -0
- package/logs/__fixtures__/secrets.js +59 -0
- package/logs/__fixtures__/vectors.js +329 -0
- package/logs/__fixtures__/with-env.js +45 -0
- package/logs/context.js +109 -0
- package/logs/contract-keys.js +67 -0
- package/logs/debug-shims.js +46 -0
- package/logs/denied-keys.js +21 -0
- package/logs/index.js +14 -3
- package/logs/jest-logger-setup.js +25 -0
- package/logs/levels.js +71 -0
- package/logs/logger-runtime.js +137 -0
- package/logs/logger.js +134 -46
- package/logs/record.js +276 -0
- package/logs/redact.js +328 -0
- package/logs/serialize.js +329 -0
- package/logs/sinks.js +93 -0
- package/logs/summarize-event.js +163 -0
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +320 -0
- package/modules/repositories/module-repository-documentdb.js +350 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +145 -0
- package/modules/repositories/module-repository-mongo.js +436 -0
- package/modules/repositories/module-repository-postgres.js +481 -0
- package/modules/repositories/module-repository.js +369 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +561 -0
- package/modules/requester/requester.js +605 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +34 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +294 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +20 -0
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +92 -52
- package/prisma-mongodb/schema.prisma +455 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
- package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
- package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
- package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
- package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
- package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
- package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +436 -0
- package/queues/queue-delivery.js +41 -0
- package/queues/queuer-util.js +89 -21
- package/reporting/README.md +154 -0
- package/reporting/builtin-reports.js +6 -0
- package/reporting/index.js +13 -0
- package/reporting/report-base.js +49 -0
- package/reporting/reports/integrations-report.js +221 -0
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/telemetry/README.md +331 -0
- package/telemetry/bind-telemetry-context.js +77 -0
- package/telemetry/canonical-counters.js +52 -0
- package/telemetry/exporters.js +85 -0
- package/telemetry/index.js +26 -0
- package/telemetry/instrument-handler.js +104 -0
- package/telemetry/no-op-telemetry.js +71 -0
- package/telemetry/north-star.js +103 -0
- package/telemetry/otel-telemetry.js +232 -0
- package/telemetry/plugin-subscribers.js +77 -0
- package/telemetry/telemetry-config.js +120 -0
- package/telemetry/telemetry-context.js +51 -0
- package/telemetry/telemetry-event-bus.js +58 -0
- package/telemetry/telemetry-runtime.js +164 -0
- package/telemetry/telemetry-service.js +52 -0
- package/telemetry/usage-rollup-subscriber.js +116 -0
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/errors/index.d.ts +24 -10
- package/types/integrations/index.d.ts +3 -8
- package/types/logs/index.d.ts +61 -2
- package/types/module-plugin/index.d.ts +20 -69
- package/types/syncs/index.d.ts +0 -17
- package/usage/README.md +54 -0
- package/usage/index.js +17 -0
- package/usage/repositories/usage-repository-documentdb.js +194 -0
- package/usage/repositories/usage-repository-factory.js +25 -0
- package/usage/repositories/usage-repository-interface.js +37 -0
- package/usage/repositories/usage-repository-prisma.js +146 -0
- package/usage/tracked-metrics.js +38 -0
- package/usage/usage-windows.js +24 -0
- package/user/repositories/user-repository-documentdb.js +426 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +214 -0
- package/user/repositories/user-repository-mongo.js +323 -0
- package/user/repositories/user-repository-postgres.js +377 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +143 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +82 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.js +0 -165
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for ProcessRepository.applyProcessUpdate() validation.
|
|
3
|
+
*
|
|
4
|
+
* These utilities are backend-agnostic: they enforce invariants on the
|
|
5
|
+
* `ProcessUpdateOps` shape BEFORE each adapter emits any SQL or database
|
|
6
|
+
* command. Keeping validation here means any bug we fix (e.g. tighter
|
|
7
|
+
* path regex, size cap) fixes all three adapters in one place.
|
|
8
|
+
*
|
|
9
|
+
* Imported by the Postgres, MongoDB, and DocumentDB adapters.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Allowed dot-path shape. Root must be `context` or `results`, and each
|
|
14
|
+
* segment after the first must be a JS-identifier-style token. Numeric
|
|
15
|
+
* segments (array indices) and bracket syntax are intentionally
|
|
16
|
+
* disallowed — array element mutation is exclusively handled via
|
|
17
|
+
* `pushSlice`, which targets a whole array at a path.
|
|
18
|
+
*/
|
|
19
|
+
const PATH_REGEX = /^(context|results)(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes and validates a `ProcessUpdateOps` object. Returns a frozen
|
|
23
|
+
* copy with defaults applied and every key pre-validated. Throws synchronously
|
|
24
|
+
* on any shape error so adapters can fail fast before touching the DB.
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} ops
|
|
27
|
+
* @returns {{
|
|
28
|
+
* increment: Record<string, number>,
|
|
29
|
+
* set: Record<string, unknown>,
|
|
30
|
+
* pushSlice: Record<string, { values: unknown[]; keepLast: number }>,
|
|
31
|
+
* newState: string|null,
|
|
32
|
+
* }}
|
|
33
|
+
*/
|
|
34
|
+
function validateOps(ops) {
|
|
35
|
+
if (!ops || typeof ops !== 'object' || Array.isArray(ops)) {
|
|
36
|
+
throw new Error('applyProcessUpdate: ops must be an object');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const increment = ops.increment || {};
|
|
40
|
+
const set = ops.set || {};
|
|
41
|
+
const pushSlice = ops.pushSlice || {};
|
|
42
|
+
const newState = ops.newState ?? null;
|
|
43
|
+
|
|
44
|
+
for (const [path, delta] of Object.entries(increment)) {
|
|
45
|
+
assertPath(path, 'increment');
|
|
46
|
+
if (typeof delta !== 'number' || !Number.isFinite(delta)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`applyProcessUpdate: increment['${path}'] must be a finite number, got ${typeof delta}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const path of Object.keys(set)) {
|
|
54
|
+
assertPath(path, 'set');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const [path, spec] of Object.entries(pushSlice)) {
|
|
58
|
+
assertPath(path, 'pushSlice');
|
|
59
|
+
if (
|
|
60
|
+
!spec ||
|
|
61
|
+
typeof spec !== 'object' ||
|
|
62
|
+
!Array.isArray(spec.values) ||
|
|
63
|
+
typeof spec.keepLast !== 'number' ||
|
|
64
|
+
!Number.isInteger(spec.keepLast) ||
|
|
65
|
+
spec.keepLast <= 0
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`applyProcessUpdate: pushSlice['${path}'] must be { values: [], keepLast: positive integer }`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (newState !== null && typeof newState !== 'string') {
|
|
74
|
+
throw new Error('applyProcessUpdate: newState must be a string');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hasAnyOp =
|
|
78
|
+
Object.keys(increment).length > 0 ||
|
|
79
|
+
Object.keys(set).length > 0 ||
|
|
80
|
+
Object.keys(pushSlice).length > 0 ||
|
|
81
|
+
newState !== null;
|
|
82
|
+
if (!hasAnyOp) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'applyProcessUpdate: at least one of increment/set/pushSlice/newState must be provided'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Object.freeze({ increment, set, pushSlice, newState });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function assertPath(path, opName) {
|
|
92
|
+
if (!PATH_REGEX.test(path)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`applyProcessUpdate: invalid path '${path}' in ${opName} (must match ${PATH_REGEX})`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Splits a validated path into `{ column, segments }`.
|
|
101
|
+
* `'context.pagination.pageCount'` → `{ column: 'context', segments: ['pagination', 'pageCount'] }`.
|
|
102
|
+
*/
|
|
103
|
+
function splitPath(path) {
|
|
104
|
+
const [column, ...segments] = path.split('.');
|
|
105
|
+
return { column, segments };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
PATH_REGEX,
|
|
110
|
+
validateOps,
|
|
111
|
+
splitPath,
|
|
112
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coerces an id to an integer, rejecting partially-numeric input ('12abc',
|
|
3
|
+
* '12.9') that parseInt would silently truncate to 12 and read the wrong record.
|
|
4
|
+
*/
|
|
5
|
+
function strictIntId(id) {
|
|
6
|
+
const str = String(id).trim();
|
|
7
|
+
if (!/^-?\d+$/.test(str)) {
|
|
8
|
+
throw new TypeError(`Invalid ID: ${id} cannot be converted to integer`);
|
|
9
|
+
}
|
|
10
|
+
return Number.parseInt(str, 10);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { strictIntId };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { IntegrationBase } = require('../../integration-base');
|
|
2
|
+
|
|
3
|
+
class ConfigCapturingModule {
|
|
4
|
+
static definition = {
|
|
5
|
+
getName: () => 'config-capturing-module'
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ConfigCapturingIntegration extends IntegrationBase {
|
|
10
|
+
static Definition = {
|
|
11
|
+
name: 'config-capturing',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
modules: {
|
|
14
|
+
primary: ConfigCapturingModule
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
label: 'Config Capturing Integration',
|
|
18
|
+
description: 'Test double for capturing config state during updates',
|
|
19
|
+
detailsUrl: 'https://example.com',
|
|
20
|
+
icon: 'test-icon'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static _capturedOnUpdateState = null;
|
|
25
|
+
|
|
26
|
+
static resetCaptures() {
|
|
27
|
+
this._capturedOnUpdateState = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static getCapturedOnUpdateState() {
|
|
31
|
+
return this._capturedOnUpdateState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
constructor(params) {
|
|
35
|
+
super(params);
|
|
36
|
+
this.integrationRepository = {
|
|
37
|
+
updateIntegrationById: jest.fn().mockResolvedValue({}),
|
|
38
|
+
findIntegrationById: jest.fn().mockResolvedValue({}),
|
|
39
|
+
};
|
|
40
|
+
this.updateIntegrationStatus = {
|
|
41
|
+
execute: jest.fn().mockResolvedValue({})
|
|
42
|
+
};
|
|
43
|
+
this.updateIntegrationMessages = {
|
|
44
|
+
execute: jest.fn().mockResolvedValue({})
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize() {
|
|
49
|
+
this.registerEventHandlers();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async onUpdate(params) {
|
|
53
|
+
ConfigCapturingIntegration._capturedOnUpdateState = {
|
|
54
|
+
thisConfig: JSON.parse(JSON.stringify(this.config)),
|
|
55
|
+
paramsConfig: params.config
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.config = this._deepMerge(this.config, params.config);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_deepMerge(target, source) {
|
|
62
|
+
const result = { ...target };
|
|
63
|
+
for (const key of Object.keys(source)) {
|
|
64
|
+
if (
|
|
65
|
+
source[key] !== null &&
|
|
66
|
+
typeof source[key] === 'object' &&
|
|
67
|
+
!Array.isArray(source[key]) &&
|
|
68
|
+
target[key] !== null &&
|
|
69
|
+
typeof target[key] === 'object' &&
|
|
70
|
+
!Array.isArray(target[key])
|
|
71
|
+
) {
|
|
72
|
+
result[key] = this._deepMerge(target[key], source[key]);
|
|
73
|
+
} else {
|
|
74
|
+
result[key] = source[key];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { ConfigCapturingIntegration };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { IntegrationBase } = require('../../integration-base');
|
|
2
|
+
|
|
3
|
+
class DummyModule {
|
|
4
|
+
static definition = {
|
|
5
|
+
getName: () => 'dummy'
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class DummyIntegration extends IntegrationBase {
|
|
10
|
+
static Definition = {
|
|
11
|
+
name: 'dummy',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
modules: {
|
|
14
|
+
dummy: DummyModule
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
label: 'Dummy Integration',
|
|
18
|
+
description: 'A dummy integration for testing',
|
|
19
|
+
detailsUrl: 'https://example.com',
|
|
20
|
+
icon: 'dummy-icon'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static getOptionDetails() {
|
|
25
|
+
return {
|
|
26
|
+
name: this.Definition.name,
|
|
27
|
+
version: this.Definition.version,
|
|
28
|
+
display: this.Definition.display
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(params) {
|
|
33
|
+
super(params);
|
|
34
|
+
this.sendSpy = jest.fn();
|
|
35
|
+
this.testAuthSpy = jest.fn();
|
|
36
|
+
this.eventCallHistory = [];
|
|
37
|
+
this.events = {};
|
|
38
|
+
|
|
39
|
+
this.integrationRepository = {
|
|
40
|
+
updateIntegrationById: jest.fn().mockResolvedValue({}),
|
|
41
|
+
findIntegrationById: jest.fn().mockResolvedValue({}),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.updateIntegrationStatus = {
|
|
45
|
+
execute: jest.fn().mockResolvedValue({})
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.updateIntegrationMessages = {
|
|
49
|
+
execute: jest.fn().mockResolvedValue({})
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async loadDynamicUserActions() {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async send(event, data) {
|
|
58
|
+
this.sendSpy(event, data);
|
|
59
|
+
this.eventCallHistory.push({ event, data, timestamp: Date.now() });
|
|
60
|
+
if (event === 'ON_UPDATE') {
|
|
61
|
+
await this.onUpdate(data);
|
|
62
|
+
}
|
|
63
|
+
if (event === 'ON_DELETE') {
|
|
64
|
+
await this.onDelete(data);
|
|
65
|
+
}
|
|
66
|
+
return { event, data };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async initialize() {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async testAuth() {
|
|
74
|
+
this.testAuthSpy();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async onCreate({ integrationId }) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async onUpdate(params) {
|
|
82
|
+
this.config = this._deepMerge(this.config, params.config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_deepMerge(target, source) {
|
|
86
|
+
const result = { ...target };
|
|
87
|
+
for (const key of Object.keys(source)) {
|
|
88
|
+
if (
|
|
89
|
+
source[key] !== null &&
|
|
90
|
+
typeof source[key] === 'object' &&
|
|
91
|
+
!Array.isArray(source[key]) &&
|
|
92
|
+
target[key] !== null &&
|
|
93
|
+
typeof target[key] === 'object' &&
|
|
94
|
+
!Array.isArray(target[key])
|
|
95
|
+
) {
|
|
96
|
+
result[key] = this._deepMerge(target[key], source[key]);
|
|
97
|
+
} else {
|
|
98
|
+
result[key] = source[key];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async onDelete(params) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getConfig() {
|
|
109
|
+
return this.config || {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { DummyIntegration };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const { v4: uuid } = require('uuid');
|
|
2
|
+
const { validateConfigPatch } = require('../../repositories/config-patch-shared');
|
|
3
|
+
|
|
4
|
+
class TestIntegrationRepository {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.store = new Map();
|
|
7
|
+
this.operationHistory = [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async createIntegration(entities, userId, config) {
|
|
11
|
+
const id = uuid();
|
|
12
|
+
const record = {
|
|
13
|
+
id,
|
|
14
|
+
_id: id,
|
|
15
|
+
entitiesIds: entities,
|
|
16
|
+
userId: userId,
|
|
17
|
+
config,
|
|
18
|
+
version: '0.0.0',
|
|
19
|
+
status: 'IN_CREATION',
|
|
20
|
+
messages: {},
|
|
21
|
+
createdAt: new Date(),
|
|
22
|
+
};
|
|
23
|
+
this.store.set(id, record);
|
|
24
|
+
this.operationHistory.push({ operation: 'create', id, userId, config });
|
|
25
|
+
return record;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findIntegrationById(id) {
|
|
29
|
+
const rec = this.store.get(id);
|
|
30
|
+
this.operationHistory.push({ operation: 'findById', id, found: !!rec });
|
|
31
|
+
if (!rec) return null;
|
|
32
|
+
return rec;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async findIntegrationsByUserId(userId) {
|
|
36
|
+
const results = Array.from(this.store.values()).filter(r => r.userId === userId);
|
|
37
|
+
this.operationHistory.push({ operation: 'findByUserId', userId, count: results.length });
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async findIntegrations(filter = {}) {
|
|
42
|
+
const { type, status } = filter;
|
|
43
|
+
const results = Array.from(this.store.values()).filter((r) => {
|
|
44
|
+
if (type && r.config?.type !== type) return false;
|
|
45
|
+
if (status && r.status !== status) return false;
|
|
46
|
+
return true;
|
|
47
|
+
});
|
|
48
|
+
this.operationHistory.push({ operation: 'findAll', count: results.length });
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async findIntegrationByUserId(userId) {
|
|
53
|
+
const record = Array.from(this.store.values()).find((r) => r.userId === userId);
|
|
54
|
+
this.operationHistory.push({
|
|
55
|
+
operation: 'findSingleByUserId',
|
|
56
|
+
userId,
|
|
57
|
+
found: !!record,
|
|
58
|
+
});
|
|
59
|
+
return record || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async findIntegrationsByEntityId(entityId) {
|
|
63
|
+
const target = String(entityId);
|
|
64
|
+
const results = Array.from(this.store.values()).filter((r) =>
|
|
65
|
+
(r.entitiesIds || []).map(String).includes(target)
|
|
66
|
+
);
|
|
67
|
+
this.operationHistory.push({
|
|
68
|
+
operation: 'findByEntityId',
|
|
69
|
+
entityId: target,
|
|
70
|
+
count: results.length,
|
|
71
|
+
});
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async updateIntegrationMessages(id, type, title, body, timestamp) {
|
|
76
|
+
const rec = this.store.get(id);
|
|
77
|
+
if (!rec) {
|
|
78
|
+
this.operationHistory.push({ operation: 'updateMessages', id, success: false });
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (!rec.messages[type]) rec.messages[type] = [];
|
|
82
|
+
rec.messages[type].push({ title, message: body, timestamp });
|
|
83
|
+
this.operationHistory.push({ operation: 'updateMessages', id, type, success: true });
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async updateIntegrationConfig(id, config) {
|
|
88
|
+
if (config === null || config === undefined) {
|
|
89
|
+
throw new Error('Config parameter is required');
|
|
90
|
+
}
|
|
91
|
+
const rec = this.store.get(id);
|
|
92
|
+
if (!rec) {
|
|
93
|
+
this.operationHistory.push({ operation: 'updateConfig', id, success: false });
|
|
94
|
+
throw new Error(`Integration with id ${id} not found`);
|
|
95
|
+
}
|
|
96
|
+
rec.config = config;
|
|
97
|
+
this.operationHistory.push({ operation: 'updateConfig', id, success: true });
|
|
98
|
+
return rec;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async patchIntegrationConfig(id, patch) {
|
|
102
|
+
validateConfigPatch(patch);
|
|
103
|
+
if (!this.store.has(id)) {
|
|
104
|
+
this.operationHistory.push({ operation: 'patchConfig', id, success: false });
|
|
105
|
+
throw new Error(`Integration with id ${id} not found`);
|
|
106
|
+
}
|
|
107
|
+
// Simulates the DB round trip real adapters make: the merge reads
|
|
108
|
+
// the record fresh at write time rather than off a snapshot taken
|
|
109
|
+
// before the await, so concurrent patches to the same id can't lose
|
|
110
|
+
// each other's keys — same guarantee as `config || $1::jsonb` in
|
|
111
|
+
// postgres or a mongo/documentdb findAndModify.
|
|
112
|
+
await Promise.resolve();
|
|
113
|
+
const rec = this.store.get(id);
|
|
114
|
+
rec.config = { ...rec.config, ...patch };
|
|
115
|
+
this.operationHistory.push({ operation: 'patchConfig', id, success: true });
|
|
116
|
+
return rec;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async deleteIntegrationById(id) {
|
|
120
|
+
const existed = this.store.has(id);
|
|
121
|
+
const result = this.store.delete(id);
|
|
122
|
+
this.operationHistory.push({ operation: 'delete', id, existed, success: result });
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async updateIntegrationStatus(id, status) {
|
|
127
|
+
const rec = this.store.get(id);
|
|
128
|
+
if (rec) {
|
|
129
|
+
rec.status = status;
|
|
130
|
+
this.operationHistory.push({ operation: 'updateStatus', id, status, success: true });
|
|
131
|
+
} else {
|
|
132
|
+
this.operationHistory.push({ operation: 'updateStatus', id, status, success: false });
|
|
133
|
+
}
|
|
134
|
+
return !!rec;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getOperationHistory() {
|
|
138
|
+
return [...this.operationHistory];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
clearHistory() {
|
|
142
|
+
this.operationHistory = [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { TestIntegrationRepository };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const {
|
|
2
|
+
assertEntityIdsInput,
|
|
3
|
+
integrationNotFound,
|
|
4
|
+
loadIntegration,
|
|
5
|
+
} = require('../utils/assert-entity-ids-input');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Attaches entities to an existing integration, e.g. when a user grants
|
|
9
|
+
* access to more accounts after the integration was created. Every entity
|
|
10
|
+
* must exist, belong to the integration's user, and be of a module the
|
|
11
|
+
* integration already holds (so hydration can load it). Entities that are
|
|
12
|
+
* already attached are left as they are.
|
|
13
|
+
* @class AddEntitiesToIntegration
|
|
14
|
+
*/
|
|
15
|
+
class AddEntitiesToIntegration {
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} params
|
|
18
|
+
* @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
|
|
19
|
+
* @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
|
|
20
|
+
*/
|
|
21
|
+
constructor({ integrationRepository, moduleRepository }) {
|
|
22
|
+
this.integrationRepository = integrationRepository;
|
|
23
|
+
this.moduleRepository = moduleRepository;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} integrationId
|
|
28
|
+
* @param {string[]} entityIds
|
|
29
|
+
* @returns {Promise<Object>} The updated integration record.
|
|
30
|
+
*/
|
|
31
|
+
async execute(integrationId, entityIds) {
|
|
32
|
+
assertEntityIdsInput(integrationId, entityIds);
|
|
33
|
+
|
|
34
|
+
const integration = await loadIntegration(
|
|
35
|
+
this.integrationRepository,
|
|
36
|
+
integrationId
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const entities = await this.moduleRepository.findEntitiesByIds(
|
|
40
|
+
entityIds
|
|
41
|
+
);
|
|
42
|
+
const foundIds = new Set(entities.map((e) => String(e.id)));
|
|
43
|
+
const missing = entityIds.filter((id) => !foundIds.has(String(id)));
|
|
44
|
+
if (missing.length > 0) {
|
|
45
|
+
const error = new Error(
|
|
46
|
+
`Entities not found: ${missing.join(', ')}`
|
|
47
|
+
);
|
|
48
|
+
error.code = 'ENTITIES_NOT_FOUND';
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const foreign = entities.filter(
|
|
53
|
+
(e) => String(e.userId) !== String(integration.userId)
|
|
54
|
+
);
|
|
55
|
+
if (foreign.length > 0) {
|
|
56
|
+
const error = new Error(
|
|
57
|
+
`Entities do not belong to the integration's user: ${foreign
|
|
58
|
+
.map((e) => e.id)
|
|
59
|
+
.join(', ')}`
|
|
60
|
+
);
|
|
61
|
+
error.code = 'ENTITY_USER_MISMATCH';
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const attached = await this.moduleRepository.findEntitiesByIds(
|
|
66
|
+
integration.entitiesIds ?? []
|
|
67
|
+
);
|
|
68
|
+
const heldModules = new Set(attached.map((e) => e.moduleName));
|
|
69
|
+
const otherModules = entities.filter(
|
|
70
|
+
(e) => !heldModules.has(e.moduleName)
|
|
71
|
+
);
|
|
72
|
+
if (otherModules.length > 0) {
|
|
73
|
+
const error = new Error(
|
|
74
|
+
`Entities belong to a module the integration does not hold: ${otherModules
|
|
75
|
+
.map((e) => `${e.id} (${e.moduleName})`)
|
|
76
|
+
.join(', ')}`
|
|
77
|
+
);
|
|
78
|
+
error.code = 'ENTITY_MODULE_MISMATCH';
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const updated =
|
|
83
|
+
await this.integrationRepository.addEntitiesToIntegration(
|
|
84
|
+
integrationId,
|
|
85
|
+
entityIds
|
|
86
|
+
);
|
|
87
|
+
if (!updated) throw integrationNotFound(integrationId);
|
|
88
|
+
return updated;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { AddEntitiesToIntegration };
|