@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,253 @@
|
|
|
1
|
+
// Removed Integration wrapper - using IntegrationBase directly
|
|
2
|
+
const {
|
|
3
|
+
mapIntegrationClassToIntegrationDTO,
|
|
4
|
+
} = require('../utils/map-integration-dto');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Use case for creating a new integration instance.
|
|
8
|
+
* @class CreateIntegration
|
|
9
|
+
*/
|
|
10
|
+
class CreateIntegration {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new CreateIntegration instance.
|
|
13
|
+
* @param {Object} params - Configuration parameters.
|
|
14
|
+
* @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository - Repository for integration data operations.
|
|
15
|
+
* @param {import('../integration-classes').IntegrationClasses} params.integrationClasses - Array of available integration classes.
|
|
16
|
+
* @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
|
|
17
|
+
*/
|
|
18
|
+
constructor({ integrationRepository, integrationClasses, moduleFactory }) {
|
|
19
|
+
this.integrationRepository = integrationRepository;
|
|
20
|
+
this.integrationClasses = integrationClasses;
|
|
21
|
+
this.moduleFactory = moduleFactory;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Executes the integration creation process. Reuses an existing
|
|
26
|
+
* integration when one already exists for the same userId, config.type,
|
|
27
|
+
* and entity set, instead of creating a duplicate.
|
|
28
|
+
* @async
|
|
29
|
+
* @param {string[]} entities - Array of entity IDs to associate with the integration.
|
|
30
|
+
* @param {string} userId - ID of the user creating the integration.
|
|
31
|
+
* @param {Object} config - Configuration object for the integration.
|
|
32
|
+
* @param {string} config.type - Type of integration to create.
|
|
33
|
+
* @returns {Promise<Object>} The created or reused integration DTO.
|
|
34
|
+
* @throws {Error} When integration class is not found for the specified type.
|
|
35
|
+
*/
|
|
36
|
+
async execute(entities, userId, config) {
|
|
37
|
+
console.log(
|
|
38
|
+
`[Frigg] Creating ${config?.type} integration for user ${userId} with entities [${entities}]`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const existing = await this._findDuplicate(
|
|
42
|
+
userId,
|
|
43
|
+
config?.type,
|
|
44
|
+
entities
|
|
45
|
+
);
|
|
46
|
+
if (existing) {
|
|
47
|
+
console.log(
|
|
48
|
+
`[Frigg] Found existing integration ${existing.id} for the same user, type, and entities — reusing it`
|
|
49
|
+
);
|
|
50
|
+
return this._reuseExisting(existing, config);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const integrationRecord =
|
|
54
|
+
await this.integrationRepository.createIntegration(
|
|
55
|
+
entities,
|
|
56
|
+
userId,
|
|
57
|
+
config
|
|
58
|
+
);
|
|
59
|
+
console.log(
|
|
60
|
+
`[Frigg] Created integration ${integrationRecord.id} for user ${userId}`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// A concurrent request may have created a matching row between the
|
|
64
|
+
// lookup above and this insert; re-check before any side effects run.
|
|
65
|
+
const survivor = await this._findSurvivor(
|
|
66
|
+
userId,
|
|
67
|
+
config?.type,
|
|
68
|
+
entities
|
|
69
|
+
);
|
|
70
|
+
if (survivor && String(survivor.id) !== String(integrationRecord.id)) {
|
|
71
|
+
console.log(
|
|
72
|
+
`[Frigg] Concurrent create detected — deleting duplicate ${integrationRecord.id} and reusing ${survivor.id}`
|
|
73
|
+
);
|
|
74
|
+
await this.integrationRepository.deleteIntegrationById(
|
|
75
|
+
integrationRecord.id
|
|
76
|
+
);
|
|
77
|
+
return this._reuseExisting(survivor, config);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const integrationInstance = await this._buildInstance(
|
|
81
|
+
integrationRecord
|
|
82
|
+
);
|
|
83
|
+
console.log(
|
|
84
|
+
`[Frigg] Sending ON_CREATE for integration ${integrationRecord.id}`
|
|
85
|
+
);
|
|
86
|
+
await integrationInstance.send('ON_CREATE', {
|
|
87
|
+
integrationId: integrationRecord.id,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return mapIntegrationClassToIntegrationDTO(integrationInstance);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async _reuseExisting(integrationRecord, config) {
|
|
94
|
+
// The caller may be reconnecting with changed settings. Persist the
|
|
95
|
+
// requested config onto the reused row as a shallow patch, so new
|
|
96
|
+
// values take effect while keys added during the original create
|
|
97
|
+
// (webhook ids, secrets) survive. `type` is re-written to the same
|
|
98
|
+
// value it dedupe-matched on, which is a harmless no-op.
|
|
99
|
+
if (config && Object.keys(config).length > 0) {
|
|
100
|
+
integrationRecord =
|
|
101
|
+
await this.integrationRepository.patchIntegrationConfig(
|
|
102
|
+
integrationRecord.id,
|
|
103
|
+
config
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const integrationInstance = await this._buildInstance(
|
|
108
|
+
integrationRecord
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (integrationInstance.status === 'IN_CREATION') {
|
|
112
|
+
// ON_CREATE never finished for this row (crashed mid-setup or is
|
|
113
|
+
// still running concurrently). Re-firing ON_CREATE here would
|
|
114
|
+
// risk re-registering a webhook a partially-completed attempt
|
|
115
|
+
// already created — unsafe without idempotent setup, which is a
|
|
116
|
+
// separate, larger change. Surfaced loudly rather than silently
|
|
117
|
+
// reused as if healthy, so it's diagnosable; testAuth below still
|
|
118
|
+
// runs and can at least surface bad credentials as ERROR.
|
|
119
|
+
console.warn(
|
|
120
|
+
`[Frigg] Integration ${integrationInstance.id} is still IN_CREATION on reuse — setup never completed`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// User is actively trying to reconnect; here if the integration is
|
|
125
|
+
// disabled, we can enable it again.
|
|
126
|
+
const authPassed = await integrationInstance.testAuth();
|
|
127
|
+
await integrationInstance.reconcileAuthStatus(authPassed);
|
|
128
|
+
if (integrationInstance.status === 'DISABLED') {
|
|
129
|
+
console.log(
|
|
130
|
+
`[Frigg] Integration ${integrationInstance.id} changed status from DISABLED to ENABLED`
|
|
131
|
+
);
|
|
132
|
+
await integrationInstance.persistStatus('ENABLED');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return mapIntegrationClassToIntegrationDTO(integrationInstance);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async _findMatches(userId, type, entityIds) {
|
|
139
|
+
const candidates =
|
|
140
|
+
await this.integrationRepository.findIntegrationsByUserId(userId);
|
|
141
|
+
const target = new Set((entityIds ?? []).map(String));
|
|
142
|
+
|
|
143
|
+
const live = candidates.filter(
|
|
144
|
+
(integration) =>
|
|
145
|
+
// An IN_DELETION row is either mid-teardown or a zombie left
|
|
146
|
+
// behind by a failed deleteIntegrationById call — never a live
|
|
147
|
+
// integration. Treating it as a duplicate would hand a
|
|
148
|
+
// reinstall attempt a row nothing will ever move out of
|
|
149
|
+
// IN_DELETION, silently discarded by the queue worker forever.
|
|
150
|
+
// Let a fresh create proceed instead; the zombie is cleaned up
|
|
151
|
+
// out-of-band, matching how teardown failures are already
|
|
152
|
+
// handled here.
|
|
153
|
+
integration.status !== 'IN_DELETION' &&
|
|
154
|
+
integration.config?.type === type
|
|
155
|
+
);
|
|
156
|
+
const entitySet = (integration) =>
|
|
157
|
+
new Set((integration.entitiesIds ?? []).map(String));
|
|
158
|
+
|
|
159
|
+
const exact = live.filter((integration) => {
|
|
160
|
+
const current = entitySet(integration);
|
|
161
|
+
return (
|
|
162
|
+
current.size === target.size &&
|
|
163
|
+
[...target].every((id) => current.has(id))
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Entities may have been attached after create (addEntitiesToIntegration),
|
|
168
|
+
// so a reconnect that sends the original entities must still find the
|
|
169
|
+
// integration that now holds them, instead of creating a second one
|
|
170
|
+
// that would share those entities.
|
|
171
|
+
const supersets = live.filter((integration) => {
|
|
172
|
+
const current = entitySet(integration);
|
|
173
|
+
return [...target].every((id) => current.has(id));
|
|
174
|
+
});
|
|
175
|
+
return { exact, supersets: target.size ? supersets : [] };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async _findDuplicate(userId, type, entityIds) {
|
|
179
|
+
const { exact, supersets } = await this._findMatches(
|
|
180
|
+
userId,
|
|
181
|
+
type,
|
|
182
|
+
entityIds
|
|
183
|
+
);
|
|
184
|
+
if (exact.length) return this._pickOldest(exact);
|
|
185
|
+
return supersets.length ? this._pickOldest(supersets) : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// After an insert the new row is itself an exact match, so the survivor
|
|
189
|
+
// is the oldest of every exact or superset match (the new row included):
|
|
190
|
+
// an older integration that already holds these entities always wins.
|
|
191
|
+
async _findSurvivor(userId, type, entityIds) {
|
|
192
|
+
const { exact, supersets } = await this._findMatches(
|
|
193
|
+
userId,
|
|
194
|
+
type,
|
|
195
|
+
entityIds
|
|
196
|
+
);
|
|
197
|
+
// supersets includes the exact matches; it is empty only for an
|
|
198
|
+
// empty entity set, where exact matching alone applies.
|
|
199
|
+
const matches = supersets.length ? supersets : exact;
|
|
200
|
+
return matches.length ? this._pickOldest(matches) : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_pickOldest(records) {
|
|
204
|
+
return [...records].sort((a, b) => {
|
|
205
|
+
const diff =
|
|
206
|
+
new Date(a.createdAt ?? 0) - new Date(b.createdAt ?? 0);
|
|
207
|
+
if (diff !== 0) return diff;
|
|
208
|
+
return String(a.id).localeCompare(String(b.id), 'en', {
|
|
209
|
+
numeric: true,
|
|
210
|
+
});
|
|
211
|
+
})[0];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async _buildInstance(integrationRecord) {
|
|
215
|
+
const integrationClass = this.integrationClasses.find(
|
|
216
|
+
(integrationClass) =>
|
|
217
|
+
integrationClass.Definition.name ===
|
|
218
|
+
integrationRecord.config.type
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (!integrationClass) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`No integration class found for type: ${integrationRecord.config.type}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const modules = [];
|
|
228
|
+
for (const entityId of integrationRecord.entitiesIds) {
|
|
229
|
+
const moduleInstance = await this.moduleFactory.getModuleInstance(
|
|
230
|
+
entityId,
|
|
231
|
+
integrationRecord.userId
|
|
232
|
+
);
|
|
233
|
+
modules.push(moduleInstance);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const integrationInstance = new integrationClass({
|
|
237
|
+
id: integrationRecord.id,
|
|
238
|
+
userId: integrationRecord.userId,
|
|
239
|
+
entities: integrationRecord.entitiesIds,
|
|
240
|
+
config: integrationRecord.config,
|
|
241
|
+
status: integrationRecord.status,
|
|
242
|
+
version: integrationRecord.version,
|
|
243
|
+
messages: integrationRecord.messages,
|
|
244
|
+
modules,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await integrationInstance.initialize();
|
|
248
|
+
|
|
249
|
+
return integrationInstance;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { CreateIntegration };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreateProcess Use Case
|
|
3
|
+
*
|
|
4
|
+
* Creates a new process record for tracking long-running operations.
|
|
5
|
+
* Validates required fields and delegates persistence to the repository.
|
|
6
|
+
*
|
|
7
|
+
* Design Philosophy:
|
|
8
|
+
* - Use cases encapsulate business logic
|
|
9
|
+
* - Validation happens at the use case layer
|
|
10
|
+
* - Repositories handle only data access
|
|
11
|
+
* - Process model is generic and reusable
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const createProcess = new CreateProcess({ processRepository });
|
|
15
|
+
* const process = await createProcess.execute({
|
|
16
|
+
* userId: 'user123',
|
|
17
|
+
* integrationId: 'integration456',
|
|
18
|
+
* name: 'zoho-crm-contact-sync',
|
|
19
|
+
* type: 'CRM_SYNC',
|
|
20
|
+
* state: 'INITIALIZING',
|
|
21
|
+
* context: { syncType: 'INITIAL', totalRecords: 0 },
|
|
22
|
+
* results: { aggregateData: { totalSynced: 0, totalFailed: 0 } }
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
const { invalidProcessData } = require('./process-errors');
|
|
26
|
+
|
|
27
|
+
class CreateProcess {
|
|
28
|
+
/**
|
|
29
|
+
* @param {Object} params
|
|
30
|
+
* @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
|
|
31
|
+
*/
|
|
32
|
+
constructor({ processRepository }) {
|
|
33
|
+
if (!processRepository) {
|
|
34
|
+
throw new Error('processRepository is required');
|
|
35
|
+
}
|
|
36
|
+
this.processRepository = processRepository;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute the use case to create a process
|
|
41
|
+
* @param {Object} processData - Process data to create
|
|
42
|
+
* @param {string} processData.userId - User ID (required)
|
|
43
|
+
* @param {string} processData.integrationId - Integration ID (required)
|
|
44
|
+
* @param {string} processData.name - Process name (required)
|
|
45
|
+
* @param {string} processData.type - Process type (required)
|
|
46
|
+
* @param {string} [processData.state='INITIALIZING'] - Initial state
|
|
47
|
+
* @param {Object} [processData.context={}] - Process context
|
|
48
|
+
* @param {Object} [processData.results={}] - Process results
|
|
49
|
+
* @param {string[]} [processData.childProcesses=[]] - Child process IDs
|
|
50
|
+
* @param {string} [processData.parentProcessId] - Parent process ID
|
|
51
|
+
* @returns {Promise<Object>} Created process record
|
|
52
|
+
* @throws {Error} If validation fails or creation errors
|
|
53
|
+
*/
|
|
54
|
+
async execute(processData) {
|
|
55
|
+
// Validate required fields
|
|
56
|
+
this._validateProcessData(processData);
|
|
57
|
+
|
|
58
|
+
// Set defaults for optional fields
|
|
59
|
+
const processToCreate = {
|
|
60
|
+
userId: processData.userId,
|
|
61
|
+
integrationId: processData.integrationId,
|
|
62
|
+
name: processData.name,
|
|
63
|
+
type: processData.type,
|
|
64
|
+
state: processData.state || 'INITIALIZING',
|
|
65
|
+
context: processData.context || {},
|
|
66
|
+
results: processData.results || {},
|
|
67
|
+
childProcesses: processData.childProcesses || [],
|
|
68
|
+
parentProcessId: processData.parentProcessId || null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Delegate to repository
|
|
72
|
+
try {
|
|
73
|
+
const createdProcess = await this.processRepository.create(processToCreate);
|
|
74
|
+
return createdProcess;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Failed to create process: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate process data
|
|
82
|
+
* @private
|
|
83
|
+
* @param {Object} processData - Process data to validate
|
|
84
|
+
* @throws {Error} If validation fails
|
|
85
|
+
*/
|
|
86
|
+
_validateProcessData(processData) {
|
|
87
|
+
const requiredFields = ['userId', 'integrationId', 'name', 'type'];
|
|
88
|
+
const missingFields = requiredFields.filter(field => !processData[field]);
|
|
89
|
+
|
|
90
|
+
if (missingFields.length > 0) {
|
|
91
|
+
throw invalidProcessData(
|
|
92
|
+
`Missing required fields for process creation: ${missingFields.join(', ')}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate field types
|
|
97
|
+
if (typeof processData.userId !== 'string') {
|
|
98
|
+
throw invalidProcessData('userId must be a string');
|
|
99
|
+
}
|
|
100
|
+
if (typeof processData.integrationId !== 'string') {
|
|
101
|
+
throw invalidProcessData('integrationId must be a string');
|
|
102
|
+
}
|
|
103
|
+
if (typeof processData.name !== 'string') {
|
|
104
|
+
throw invalidProcessData('name must be a string');
|
|
105
|
+
}
|
|
106
|
+
if (typeof processData.type !== 'string') {
|
|
107
|
+
throw invalidProcessData('type must be a string');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate optional fields if provided
|
|
111
|
+
if (processData.state && typeof processData.state !== 'string') {
|
|
112
|
+
throw invalidProcessData('state must be a string');
|
|
113
|
+
}
|
|
114
|
+
if (processData.context && typeof processData.context !== 'object') {
|
|
115
|
+
throw invalidProcessData('context must be an object');
|
|
116
|
+
}
|
|
117
|
+
if (processData.results && typeof processData.results !== 'object') {
|
|
118
|
+
throw invalidProcessData('results must be an object');
|
|
119
|
+
}
|
|
120
|
+
if (processData.childProcesses && !Array.isArray(processData.childProcesses)) {
|
|
121
|
+
throw invalidProcessData('childProcesses must be an array');
|
|
122
|
+
}
|
|
123
|
+
if (processData.parentProcessId && typeof processData.parentProcessId !== 'string') {
|
|
124
|
+
throw invalidProcessData('parentProcessId must be a string');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { CreateProcess };
|
|
130
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
const { getLogger } = require('../../logs');
|
|
3
|
+
|
|
4
|
+
const log = getLogger('frigg.integrations');
|
|
5
|
+
// Removed Integration wrapper - using IntegrationBase directly
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Use case for deleting an integration for a specific user.
|
|
9
|
+
* @class DeleteIntegrationForUser
|
|
10
|
+
*/
|
|
11
|
+
class DeleteIntegrationForUser {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new DeleteIntegrationForUser instance.
|
|
14
|
+
* @param {Object} params - Configuration parameters.
|
|
15
|
+
* @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository - Repository for integration data operations.
|
|
16
|
+
* @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
|
|
17
|
+
* @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
|
|
18
|
+
*/
|
|
19
|
+
constructor({ integrationRepository, integrationClasses, moduleFactory }) {
|
|
20
|
+
/**
|
|
21
|
+
* @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
|
|
22
|
+
*/
|
|
23
|
+
this.integrationRepository = integrationRepository;
|
|
24
|
+
this.integrationClasses = integrationClasses;
|
|
25
|
+
this.moduleFactory = moduleFactory;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Executes the deletion of an integration for a user.
|
|
30
|
+
* @async
|
|
31
|
+
* @param {string} integrationId - ID of the integration to delete.
|
|
32
|
+
* @param {string} userId - ID of the user requesting the deletion.
|
|
33
|
+
* @returns {Promise<void>} Resolves when the integration is successfully deleted.
|
|
34
|
+
* @throws {Boom.notFound} When integration with the specified ID does not exist.
|
|
35
|
+
* @throws {Error} When the integration doesn't belong to the specified user.
|
|
36
|
+
*/
|
|
37
|
+
async execute(integrationId, userId) {
|
|
38
|
+
const integrationRecord =
|
|
39
|
+
await this.integrationRepository.findIntegrationById(integrationId);
|
|
40
|
+
|
|
41
|
+
if (!integrationRecord) {
|
|
42
|
+
throw Boom.notFound(
|
|
43
|
+
`Integration with id of ${integrationId} does not exist`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const integrationClass = this.integrationClasses.find(
|
|
48
|
+
(integrationClass) =>
|
|
49
|
+
integrationClass.Definition.name ===
|
|
50
|
+
integrationRecord.config.type
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (integrationRecord.userId !== userId) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Integration ${integrationId} does not belong to User ${userId}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Load modules with API clients for webhook deletion
|
|
60
|
+
const modules = [];
|
|
61
|
+
const failedModuleLoads = [];
|
|
62
|
+
|
|
63
|
+
for (const entityId of integrationRecord.entitiesIds) {
|
|
64
|
+
try {
|
|
65
|
+
const moduleInstance = await this.moduleFactory.getModuleInstance(
|
|
66
|
+
entityId,
|
|
67
|
+
integrationRecord.userId
|
|
68
|
+
);
|
|
69
|
+
modules.push(moduleInstance);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
log.warn('Failed to load module for deletion', {
|
|
72
|
+
eventName: 'frigg.integrations.delete_module_load_failed',
|
|
73
|
+
integrationId,
|
|
74
|
+
entityId,
|
|
75
|
+
error,
|
|
76
|
+
});
|
|
77
|
+
failedModuleLoads.push({ entityId, error: error.message });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (failedModuleLoads.length > 0) {
|
|
82
|
+
log.warn(
|
|
83
|
+
'Some modules failed to load. Webhooks for these modules may require manual cleanup.',
|
|
84
|
+
{
|
|
85
|
+
eventName: 'frigg.integrations.delete_modules_incomplete',
|
|
86
|
+
integrationId,
|
|
87
|
+
failedCount: failedModuleLoads.length,
|
|
88
|
+
totalCount: integrationRecord.entitiesIds.length,
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const integrationInstance = new integrationClass({
|
|
94
|
+
id: integrationRecord.id,
|
|
95
|
+
userId: integrationRecord.userId,
|
|
96
|
+
entities: integrationRecord.entitiesIds,
|
|
97
|
+
config: integrationRecord.config,
|
|
98
|
+
status: integrationRecord.status,
|
|
99
|
+
version: integrationRecord.version,
|
|
100
|
+
messages: integrationRecord.messages,
|
|
101
|
+
modules,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Complete async initialization (load dynamic actions, register handlers)
|
|
105
|
+
await integrationInstance.initialize();
|
|
106
|
+
|
|
107
|
+
await integrationInstance.persistStatus('IN_DELETION');
|
|
108
|
+
try {
|
|
109
|
+
await integrationInstance.send('ON_DELETE');
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// The error text can echo a request with its credentials and
|
|
112
|
+
// messages reach end users; the boundary logs the cause once.
|
|
113
|
+
await integrationInstance.updateIntegrationMessages.execute(
|
|
114
|
+
integrationId,
|
|
115
|
+
'errors',
|
|
116
|
+
'Integration Deletion Error',
|
|
117
|
+
'Deletion did not complete. Contact support.',
|
|
118
|
+
Date.now()
|
|
119
|
+
);
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Integration ${integrationId} deletion did not complete`,
|
|
122
|
+
{ cause: error }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this.integrationRepository.deleteIntegrationById(integrationId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { DeleteIntegrationForUser };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse-lookup use case: resolve an externalId (e.g. HubSpot portalId,
|
|
3
|
+
* Slack team_id, Asana workspace_id) to a single integration ID.
|
|
4
|
+
*
|
|
5
|
+
* Refuses to pick on ambiguous resolution at either layer:
|
|
6
|
+
* - more than one matching Entity row for the (externalId, moduleName) tuple
|
|
7
|
+
* - more than one Integration owning the matched entity
|
|
8
|
+
*
|
|
9
|
+
* A silent first-match in either case is a cross-tenant routing risk.
|
|
10
|
+
* Callers that expect a one-to-many fan-out should use
|
|
11
|
+
* {@link ListIntegrationsByEntityExternalIdUseCase} instead.
|
|
12
|
+
*/
|
|
13
|
+
class FindIntegrationByEntityExternalIdUseCase {
|
|
14
|
+
constructor({ integrationRepository, moduleRepository } = {}) {
|
|
15
|
+
if (!integrationRepository) {
|
|
16
|
+
throw new Error('integrationRepository is required');
|
|
17
|
+
}
|
|
18
|
+
if (!moduleRepository) {
|
|
19
|
+
throw new Error('moduleRepository is required');
|
|
20
|
+
}
|
|
21
|
+
this.integrationRepository = integrationRepository;
|
|
22
|
+
this.moduleRepository = moduleRepository;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async execute({ externalId, moduleName } = {}) {
|
|
26
|
+
if (!externalId) return null;
|
|
27
|
+
|
|
28
|
+
const filter = { externalId: String(externalId) };
|
|
29
|
+
if (moduleName) filter.moduleName = moduleName;
|
|
30
|
+
|
|
31
|
+
const entities = await this.moduleRepository.findEntities(filter);
|
|
32
|
+
if (!entities || entities.length === 0) {
|
|
33
|
+
console.log(
|
|
34
|
+
`[Frigg] findIntegrationByEntityExternalId: no entity for externalId=${externalId}${
|
|
35
|
+
moduleName ? ` moduleName=${moduleName}` : ''
|
|
36
|
+
}`
|
|
37
|
+
);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (entities.length > 1) {
|
|
41
|
+
const ids = entities.map((e) => e.id).join(', ');
|
|
42
|
+
throw new Error(
|
|
43
|
+
`findIntegrationByEntityExternalId: ambiguous resolution — externalId=${externalId}` +
|
|
44
|
+
`${moduleName ? ` moduleName=${moduleName}` : ''} matches ${entities.length} entities [${ids}]. ` +
|
|
45
|
+
`Refusing to pick one to avoid cross-tenant routing. ` +
|
|
46
|
+
`Pass a moduleName, or use listIntegrationsByEntityExternalId if multiple integrations are expected.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const entity = entities[0];
|
|
51
|
+
const integrations =
|
|
52
|
+
await this.integrationRepository.findIntegrationsByEntityId(
|
|
53
|
+
entity.id
|
|
54
|
+
);
|
|
55
|
+
if (!integrations || integrations.length === 0) {
|
|
56
|
+
console.log(
|
|
57
|
+
`[Frigg] findIntegrationByEntityExternalId: entity ${entity.id} has no owning integrations (orphan)`
|
|
58
|
+
);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (integrations.length > 1) {
|
|
62
|
+
const ids = integrations.map((i) => i.id).join(', ');
|
|
63
|
+
throw new Error(
|
|
64
|
+
`findIntegrationByEntityExternalId: ambiguous resolution — externalId=${externalId}` +
|
|
65
|
+
`${moduleName ? ` moduleName=${moduleName}` : ''} maps to ${integrations.length} integrations [${ids}]. ` +
|
|
66
|
+
`Refusing to pick one to avoid cross-tenant routing. ` +
|
|
67
|
+
`Use listIntegrationsByEntityExternalId if a one-to-many fan-out is intended.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return integrations[0].id;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { FindIntegrationByEntityExternalIdUseCase };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
class FindIntegrationContextByExternalEntityIdUseCase {
|
|
2
|
+
constructor({
|
|
3
|
+
integrationRepository,
|
|
4
|
+
moduleRepository,
|
|
5
|
+
loadIntegrationContextUseCase,
|
|
6
|
+
} = {}) {
|
|
7
|
+
if (!integrationRepository) {
|
|
8
|
+
throw new Error('integrationRepository is required');
|
|
9
|
+
}
|
|
10
|
+
if (!moduleRepository) {
|
|
11
|
+
throw new Error('moduleRepository is required');
|
|
12
|
+
}
|
|
13
|
+
if (!loadIntegrationContextUseCase) {
|
|
14
|
+
throw new Error('loadIntegrationContextUseCase is required');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.integrationRepository = integrationRepository;
|
|
18
|
+
this.moduleRepository = moduleRepository;
|
|
19
|
+
this.loadIntegrationContextUseCase = loadIntegrationContextUseCase;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async execute({ externalId, type }) {
|
|
23
|
+
if (!externalId) {
|
|
24
|
+
const error = new Error('externalId is required');
|
|
25
|
+
error.code = 'EXTERNAL_ID_REQUIRED';
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!type) {
|
|
30
|
+
const error = new Error('type is required');
|
|
31
|
+
error.code = 'TYPE_REQUIRED';
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const entity = await this.moduleRepository.findEntity({
|
|
36
|
+
externalId,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!entity) {
|
|
40
|
+
const error = new Error(
|
|
41
|
+
`Entity not found for externalId: ${externalId}`
|
|
42
|
+
);
|
|
43
|
+
error.code = 'ENTITY_NOT_FOUND';
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const integrations =
|
|
48
|
+
await this.integrationRepository.findIntegrationsByEntityId(
|
|
49
|
+
entity.id
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const integrationRecord = integrations?.find(
|
|
53
|
+
(i) => i.config?.type === type
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!integrationRecord) {
|
|
57
|
+
const error = new Error(
|
|
58
|
+
`Integration of type '${type}' not found for entity: ${entity.id}`
|
|
59
|
+
);
|
|
60
|
+
error.code = 'INTEGRATION_NOT_FOUND';
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const context = await this.loadIntegrationContextUseCase.execute({
|
|
65
|
+
integrationRecord,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
context,
|
|
70
|
+
entity,
|
|
71
|
+
record: integrationRecord,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { FindIntegrationContextByExternalEntityIdUseCase };
|