@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
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
# CLAUDE.md - Frigg Framework Core Package
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with the Frigg Framework's core package (`@friggframework/core`).
|
|
4
|
+
|
|
5
|
+
## Critical Context (Read First)
|
|
6
|
+
|
|
7
|
+
- **Package Purpose**: Core framework functionality for building enterprise serverless integrations
|
|
8
|
+
- **Main Architecture**: Hexagonal/DDD architecture with clear separation of adapters, use cases, and repositories
|
|
9
|
+
- **Key Technologies**: Node.js, Express, AWS Lambda, MongoDB/PostgreSQL (Prisma), AWS KMS encryption
|
|
10
|
+
- **Core Value**: Provides building blocks for integration developers - they extend IntegrationBase and use framework services
|
|
11
|
+
- **Security Model**: Field-level encryption, OAuth2 flows, signature validation, VPC deployment
|
|
12
|
+
- **DO NOT**: Bypass architectural layers, skip encryption for sensitive data, expose internal errors to users
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
1. [Package Overview](#package-overview)
|
|
17
|
+
2. [Architecture Principles](#architecture-principles)
|
|
18
|
+
3. [Essential Commands](#essential-commands)
|
|
19
|
+
4. [Directory Structure](#directory-structure)
|
|
20
|
+
5. [Core Components](#core-components)
|
|
21
|
+
6. [Development Workflow](#development-workflow)
|
|
22
|
+
7. [Testing Strategy](#testing-strategy)
|
|
23
|
+
8. [Anti-Patterns](#anti-patterns)
|
|
24
|
+
|
|
25
|
+
## Package Overview
|
|
26
|
+
|
|
27
|
+
`@friggframework/core` is the foundational package of the Frigg Framework, providing:
|
|
28
|
+
|
|
29
|
+
- **IntegrationBase**: Base class all integrations extend
|
|
30
|
+
- **Database Layer**: Multi-database support (MongoDB, DocumentDB, PostgreSQL) with Prisma ORM
|
|
31
|
+
- **Encryption**: Transparent field-level encryption with AWS KMS or AES
|
|
32
|
+
- **User Management**: Individual and organizational user support
|
|
33
|
+
- **Module System**: API module loading and credential management
|
|
34
|
+
- **Lambda Runtime**: Handler factory, worker base class, timeout management
|
|
35
|
+
- **Error Handling**: Standardized error types with proper HTTP status codes
|
|
36
|
+
- **Event System**: Integration lifecycle events and user actions
|
|
37
|
+
|
|
38
|
+
## Architecture Principles
|
|
39
|
+
|
|
40
|
+
### Hexagonal Architecture (Ports and Adapters)
|
|
41
|
+
|
|
42
|
+
The core package strictly follows hexagonal architecture:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌─────────────────────────────────────────────────────────┐
|
|
46
|
+
│ Adapters (Inbound) │
|
|
47
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
48
|
+
│ │ HTTP/REST │ │ Lambda │ │ SQS Workers │ │
|
|
49
|
+
│ │ (handlers/) │ │ (core/) │ │ (queues/) │ │
|
|
50
|
+
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
51
|
+
└─────────┼─────────────────┼─────────────────┼───────────┘
|
|
52
|
+
│ │ │
|
|
53
|
+
┌─────────▼─────────────────▼─────────────────▼───────────┐
|
|
54
|
+
│ Application Layer (Use Cases) │
|
|
55
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
56
|
+
│ │ CreateIntegration, UpdateIntegration, │ │
|
|
57
|
+
│ │ LoginUser, ProcessAttachmentJob, etc. │ │
|
|
58
|
+
│ └────────────────────┬───────────────────────────┘ │
|
|
59
|
+
└───────────────────────┼─────────────────────────────────┘
|
|
60
|
+
│ calls
|
|
61
|
+
┌───────────────────────▼─────────────────────────────────┐
|
|
62
|
+
│ Domain Layer (Entities) │
|
|
63
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
64
|
+
│ │ IntegrationBase, User, Credential, Entity │ │
|
|
65
|
+
│ └────────────────────┬───────────────────────────┘ │
|
|
66
|
+
└───────────────────────┼─────────────────────────────────┘
|
|
67
|
+
│ persisted by
|
|
68
|
+
┌───────────────────────▼─────────────────────────────────┐
|
|
69
|
+
│ Infrastructure Layer (Repositories) │
|
|
70
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
71
|
+
│ │ IntegrationRepository, UserRepository, │ │
|
|
72
|
+
│ │ CredentialRepository, ModuleRepository │ │
|
|
73
|
+
│ └────────────────────┬───────────────────────────┘ │
|
|
74
|
+
└───────────────────────┼─────────────────────────────────┘
|
|
75
|
+
│ accesses
|
|
76
|
+
┌───────────────────────▼─────────────────────────────────┐
|
|
77
|
+
│ External Systems │
|
|
78
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
79
|
+
│ │ MongoDB │ │ Postgres │ │ AWS KMS │ │
|
|
80
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
81
|
+
└─────────────────────────────────────────────────────────┘
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Golden Rules
|
|
85
|
+
|
|
86
|
+
1. **Handlers NEVER call repositories directly** - Always go through use cases
|
|
87
|
+
2. **Use cases contain business logic** - Not repositories or handlers
|
|
88
|
+
3. **Repositories are pure data access** - No business logic or orchestration
|
|
89
|
+
4. **Domain entities have behavior** - Not just data bags
|
|
90
|
+
5. **Encryption is transparent** - Application code works with plain data
|
|
91
|
+
|
|
92
|
+
## Essential Commands
|
|
93
|
+
|
|
94
|
+
### Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Install dependencies
|
|
98
|
+
npm install
|
|
99
|
+
|
|
100
|
+
# Generate Prisma clients (both MongoDB and PostgreSQL)
|
|
101
|
+
npm run prisma:generate
|
|
102
|
+
|
|
103
|
+
# Format and lint code
|
|
104
|
+
npm run lint:fix
|
|
105
|
+
|
|
106
|
+
# Run tests
|
|
107
|
+
npm test
|
|
108
|
+
|
|
109
|
+
# Run specific test file
|
|
110
|
+
npm test -- path/to/test.test.js
|
|
111
|
+
|
|
112
|
+
# Run tests for specific pattern
|
|
113
|
+
npm test -- --testPathPattern="encryption"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Prisma Database Operations
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Generate clients
|
|
120
|
+
npm run prisma:generate:mongo # MongoDB only
|
|
121
|
+
npm run prisma:generate:postgres # PostgreSQL only
|
|
122
|
+
npm run prisma:generate # Both databases
|
|
123
|
+
|
|
124
|
+
# Database migrations
|
|
125
|
+
npm run prisma:push:mongo # Push MongoDB schema
|
|
126
|
+
npm run prisma:migrate:postgres # Run PostgreSQL migrations
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Testing
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# All tests
|
|
133
|
+
npm test
|
|
134
|
+
|
|
135
|
+
# Specific test categories
|
|
136
|
+
npm test -- database/encryption/ # Encryption tests
|
|
137
|
+
npm test -- integrations/ # Integration tests
|
|
138
|
+
npm test -- handlers/ # Handler tests
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Directory Structure
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
packages/core/
|
|
145
|
+
├── application/ # Application-level commands and initialization
|
|
146
|
+
│ └── commands/ # Command pattern implementations
|
|
147
|
+
├── assertions/ # Validation and assertion utilities
|
|
148
|
+
├── associations/ # Entity association management
|
|
149
|
+
├── core/ # Runtime system (Lambda, Workers, Delegates)
|
|
150
|
+
│ └── CLAUDE.md # Detailed core runtime documentation
|
|
151
|
+
├── credential/ # Credential management
|
|
152
|
+
│ ├── repositories/ # Credential data access
|
|
153
|
+
│ └── use-cases/ # Credential business logic
|
|
154
|
+
├── database/ # Database layer and encryption
|
|
155
|
+
│ ├── encryption/ # Field-level encryption system
|
|
156
|
+
│ │ └── README.md # Comprehensive encryption documentation
|
|
157
|
+
│ ├── models/ # Mongoose models
|
|
158
|
+
│ ├── repositories/ # Database repositories
|
|
159
|
+
│ └── use-cases/ # Database health and management
|
|
160
|
+
├── encrypt/ # Cryptor adapter for AWS KMS and AES
|
|
161
|
+
├── errors/ # Error type definitions
|
|
162
|
+
├── handlers/ # HTTP/Lambda request handlers
|
|
163
|
+
│ └── routers/ # Express routers
|
|
164
|
+
├── integrations/ # Integration domain and lifecycle
|
|
165
|
+
│ ├── integration-base.js # Base class for all integrations
|
|
166
|
+
│ ├── repositories/ # Integration data access
|
|
167
|
+
│ ├── tests/ # Integration tests
|
|
168
|
+
│ └── use-cases/ # Integration business logic
|
|
169
|
+
├── lambda/ # AWS Lambda utilities
|
|
170
|
+
├── logs/ # Logging system
|
|
171
|
+
├── modules/ # API module system
|
|
172
|
+
│ ├── requester/ # HTTP client implementations
|
|
173
|
+
│ └── repositories/ # Module data access
|
|
174
|
+
├── prisma-mongodb/ # MongoDB Prisma schema
|
|
175
|
+
├── prisma-postgresql/ # PostgreSQL Prisma schema
|
|
176
|
+
├── queues/ # SQS job queue management
|
|
177
|
+
├── syncs/ # Data synchronization
|
|
178
|
+
├── token/ # Token management
|
|
179
|
+
│ └── repositories/ # Token data access
|
|
180
|
+
├── types/ # TypeScript type definitions
|
|
181
|
+
├── user/ # User management
|
|
182
|
+
│ ├── repositories/ # User data access
|
|
183
|
+
│ └── use-cases/ # User business logic
|
|
184
|
+
├── utils/ # Utility functions
|
|
185
|
+
├── websocket/ # WebSocket connection management
|
|
186
|
+
│ └── repositories/ # WebSocket data access
|
|
187
|
+
├── index.js # Main export file
|
|
188
|
+
├── package.json # Package configuration
|
|
189
|
+
└── README.md # Package documentation
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Core Components
|
|
193
|
+
|
|
194
|
+
### 1. Integration System (`/integrations`)
|
|
195
|
+
|
|
196
|
+
**Purpose**: Foundation for building integrations between external systems.
|
|
197
|
+
|
|
198
|
+
**Key Files**:
|
|
199
|
+
- `integration-base.js` - Base class all integrations extend
|
|
200
|
+
- `integration.js` - Integration domain aggregate using Proxy pattern
|
|
201
|
+
- `options.js` - Integration configuration and options
|
|
202
|
+
|
|
203
|
+
**Use Cases**:
|
|
204
|
+
- `create-integration.js` - Create new integration instance
|
|
205
|
+
- `update-integration.js` - Update integration configuration
|
|
206
|
+
- `delete-integration-for-user.js` - Remove integration
|
|
207
|
+
- `get-integration-instance.js` - Load integration with modules
|
|
208
|
+
- `load-integration-context.js` - Full integration context loading
|
|
209
|
+
- `add-entities-to-integration.js` / `remove-entities-from-integration.js` - Attach or detach entities on an existing integration
|
|
210
|
+
|
|
211
|
+
**Repositories**:
|
|
212
|
+
- `integration-repository-factory.js` - Creates database-specific repositories
|
|
213
|
+
- `integration-repository-mongo.js` - MongoDB implementation
|
|
214
|
+
- `integration-repository-postgres.js` - PostgreSQL implementation
|
|
215
|
+
- `addEntitiesToIntegration(integrationId, entityIds)` /
|
|
216
|
+
`removeEntitiesFromIntegration(integrationId, entityIds)` change the entity
|
|
217
|
+
set of an existing integration (all three adapters; `null` for an unknown
|
|
218
|
+
integration). Reach them through the `addEntitiesToIntegration` /
|
|
219
|
+
`removeEntitiesFromIntegration` commands. The add use case
|
|
220
|
+
(`add-entities-to-integration.js`) rejects unknown entities
|
|
221
|
+
(`ENTITIES_NOT_FOUND`, 404), entities of another user
|
|
222
|
+
(`ENTITY_USER_MISMATCH`, 403) and entities of a module the integration does
|
|
223
|
+
not already hold (`ENTITY_MODULE_MISMATCH`, 400). The remove use case
|
|
224
|
+
(`remove-entities-from-integration.js`) refuses to leave a module with no
|
|
225
|
+
entity (`LAST_ENTITY_OF_MODULE`, 409). Re-adding an attached entity or
|
|
226
|
+
removing a detached one changes nothing; a detached entity is not deleted.
|
|
227
|
+
`integration-repository-entities-parity.test.js` runs every adapter against
|
|
228
|
+
real databases when `INTEGRATION_ENTITIES_PARITY_MONGO_URL` /
|
|
229
|
+
`INTEGRATION_ENTITIES_PARITY_POSTGRES_URL` are set. The DocumentDB adapter
|
|
230
|
+
writes only `Integration.entityIds` (as its `createIntegration` does), not
|
|
231
|
+
`Entity.integrationIds`.
|
|
232
|
+
**One credential, many accounts**: when one credential covers several
|
|
233
|
+
provider accounts, give each account its own entity (externalId = account
|
|
234
|
+
id) and attach them all to one integration; webhooks then route with
|
|
235
|
+
`findIntegrationByEntityExternalId`. Traps:
|
|
236
|
+
(1) `IntegrationBase` keys modules by module name, so `this.<moduleKey>`
|
|
237
|
+
holds only one entity's module, and on the queue/webhook path
|
|
238
|
+
(`get-integration-instance.js`) `this.entities` holds entity **ids**, not
|
|
239
|
+
records. For per-account work (a webhook for one account, teardown of every
|
|
240
|
+
account in `onDelete`), load
|
|
241
|
+
`this.commands.loadIntegrationContextById(this.id)` and use
|
|
242
|
+
`context.modules`: one module per entity, each with its record on
|
|
243
|
+
`module.entity` (pick by `module.entity.externalId`).
|
|
244
|
+
(2) `findIntegrationByEntityExternalId` counts every entity with that
|
|
245
|
+
externalId, attached or not, across users: reuse an existing entity
|
|
246
|
+
(`findEntity`) when an account comes back, or delete it when it is revoked,
|
|
247
|
+
never create a second one.
|
|
248
|
+
(3) `createIntegration` reuses an existing integration of the same user
|
|
249
|
+
and type whose entities include all requested ones (an exact match wins),
|
|
250
|
+
so a reconnect that sends the original entities finds the integration that
|
|
251
|
+
now holds more.
|
|
252
|
+
(4) Every module's delegate is the integration: an auth failure on any
|
|
253
|
+
account marks the shared credential invalid and the integration ERROR.
|
|
254
|
+
- `integration-mapping-repository-*.js` - Mapping data persistence.
|
|
255
|
+
`queryMappings(integrationId, { where, orderBy, skip, take, omit })`
|
|
256
|
+
returns `{ mappings, total }`: one filtered, ordered page of an
|
|
257
|
+
integration's mappings, for callers that cannot load every row through
|
|
258
|
+
`findMappingsByIntegration`. `where` is an array of ANDed conditions
|
|
259
|
+
(at most 20); an entry may be `{ anyOf: [...] }`, ORed, one level deep.
|
|
260
|
+
Conditions are `{ path: 'mapping.<segment>...', op: 'exists' | 'notExists' }`
|
|
261
|
+
(JSON null counts as absent), `{ path: 'mapping.…', op: 'in', value: string[] }`
|
|
262
|
+
(1–500 strings) and `{ path: 'sourceId', op: 'notStartsWith', value }`
|
|
263
|
+
(a NULL sourceId matches). `orderBy` is `{ path: 'mapping.…', direction:
|
|
264
|
+
'asc' | 'desc' }`, nulls last, ties broken by id in the same direction;
|
|
265
|
+
without it rows come in id order. `take` is 1–500. `omit` lists top-level
|
|
266
|
+
mapping keys to leave out of the rows; never write such rows back. Path
|
|
267
|
+
segments must match `^[A-Za-z_][A-Za-z0-9_]*$`. The PostgreSQL, MongoDB and
|
|
268
|
+
DocumentDB adapters give the same pages and totals;
|
|
269
|
+
`integration-mapping-repository-query-parity.test.js` checks that against
|
|
270
|
+
real databases when `QUERY_MAPPINGS_PARITY_MONGO_URL` /
|
|
271
|
+
`QUERY_MAPPINGS_PARITY_POSTGRES_URL` are set. Two orderings still differ:
|
|
272
|
+
strings compare by the database collation on PostgreSQL and by code point
|
|
273
|
+
on MongoDB and DocumentDB, and arrays or objects at the sort path order
|
|
274
|
+
among themselves only on PostgreSQL (the other adapters fall back to id).
|
|
275
|
+
The legacy `IntegrationMappingRepository` inherits the port's
|
|
276
|
+
"not supported by this database adapter yet" error. Every adapter refuses
|
|
277
|
+
to run while field-level encryption still encrypts
|
|
278
|
+
`IntegrationMapping.mapping` on write (see `database/encryption/README.md`),
|
|
279
|
+
and returns rows decrypted like `findMappingsByIntegration`. PostgreSQL and
|
|
280
|
+
MongoDB decrypt through `decryptQueriedMappings`
|
|
281
|
+
(`database/encryption/integration-mapping-encryption.js`), a no-op while
|
|
282
|
+
encryption is off or lists no `IntegrationMapping` field besides `mapping`.
|
|
283
|
+
Validation lives in `integration-mapping-query.js`; a new operator is one
|
|
284
|
+
entry in its `OPERATORS` table, one in the Postgres adapter's
|
|
285
|
+
`CONDITION_SQL` and one in `CONDITION_EXPRESSIONS` in
|
|
286
|
+
`integration-mapping-query-pipeline.js`, the aggregation stages both
|
|
287
|
+
MongoDB-protocol adapters share. Those stages may only use what Amazon
|
|
288
|
+
DocumentDB 4.0 and 5.0 support (no `$facet`, `$getField`, `$set` or
|
|
289
|
+
`$unset`).
|
|
290
|
+
**Cost**: PostgreSQL answers in one SQL statement, which reads every row
|
|
291
|
+
of the integration and evaluates the JSON paths per row, because no JSON
|
|
292
|
+
index exists. With ~4 KB mappings on PostgreSQL 16 that is about 0.3 s per
|
|
293
|
+
call at 10⁴ rows per integration and 2.5–3 s at 10⁵. MongoDB answers in one
|
|
294
|
+
aggregate: the `integrationId` index narrows `$match` to the integration,
|
|
295
|
+
the `$expr` is then evaluated per document, and `$facet` returns the page
|
|
296
|
+
with its `$count`, so a page (after `omit`) must fit the 16 MB document
|
|
297
|
+
limit. DocumentDB has no `$facet`: the page and the count are two
|
|
298
|
+
concurrent aggregates, so the total does not come from the same snapshot
|
|
299
|
+
as the page. Both sort in memory on a computed key (`allowDiskUse`). With
|
|
300
|
+
~4 KB mappings on MongoDB 7 that is about 0.05–0.1 s per call at 10⁴ rows
|
|
301
|
+
per integration and 0.5–0.7 s at 10⁵ (the DocumentDB pipeline, run on
|
|
302
|
+
MongoDB, 0.6–0.8 s). On every adapter a deep offset or an empty page past
|
|
303
|
+
the end costs about the same as the first page.
|
|
304
|
+
- `process-repository-*.js` - Process (long-running job) persistence.
|
|
305
|
+
Implements `applyProcessUpdate(processId, ops)` — a race-safe alternative
|
|
306
|
+
to `update(id, patch)` that routes increments, sets, and bounded-array
|
|
307
|
+
pushes through each backend's native atomic primitive (PostgreSQL
|
|
308
|
+
`jsonb_set` / MongoDB `$inc`/`$set`/`$push`). Use this method any time
|
|
309
|
+
multiple queue workers may concurrently mutate the same Process row
|
|
310
|
+
(counters, flags, error history). The legacy `update(id, patch)`
|
|
311
|
+
remains available but is clobber-prone under concurrency.
|
|
312
|
+
|
|
313
|
+
**Integration developers extend IntegrationBase**:
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
const { IntegrationBase } = require('@friggframework/core');
|
|
317
|
+
|
|
318
|
+
class MyIntegration extends IntegrationBase {
|
|
319
|
+
static Definition = {
|
|
320
|
+
name: 'my-integration',
|
|
321
|
+
version: '1.0.0',
|
|
322
|
+
modules: {
|
|
323
|
+
serviceA: 'service-a',
|
|
324
|
+
serviceB: 'service-b'
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
async onCreate({ integrationId }) {
|
|
329
|
+
// Setup logic
|
|
330
|
+
await super.onCreate({ integrationId });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 2. Database Layer (`/database`)
|
|
336
|
+
|
|
337
|
+
**Purpose**: Multi-database support with transparent encryption.
|
|
338
|
+
|
|
339
|
+
**Key Components**:
|
|
340
|
+
- `prisma.js` - Prisma client initialization with encryption extension
|
|
341
|
+
- `mongo.js` - Mongoose connection management (legacy)
|
|
342
|
+
- `models/` - Mongoose model definitions
|
|
343
|
+
|
|
344
|
+
**Encryption System** (`/database/encryption`):
|
|
345
|
+
- **Transparent encryption**: Application code never sees encrypted data
|
|
346
|
+
- **Database-agnostic**: Works with MongoDB and PostgreSQL
|
|
347
|
+
- **AWS KMS or AES**: Production KMS, development AES
|
|
348
|
+
- **Configurable**: Via environment variables and app definition
|
|
349
|
+
|
|
350
|
+
**See**: `database/encryption/README.md` for comprehensive documentation
|
|
351
|
+
|
|
352
|
+
**Repositories**:
|
|
353
|
+
- `health-check-repository.js` - Database health monitoring
|
|
354
|
+
- `token-repository.js` - Authentication tokens
|
|
355
|
+
- `websocket-connection-repository.js` - WebSocket connections
|
|
356
|
+
- DocumentDB-enabled adapters mirror the MongoDB APIs but execute raw commands (`$runCommandRaw`, `$aggregateRaw`) for compatibility; encrypted models (e.g., credentials) still delegate reads to Prisma so the encryption extension can decrypt secrets transparently.
|
|
357
|
+
|
|
358
|
+
**Use Cases**:
|
|
359
|
+
- `check-database-health-use-case.js` - Database health checks
|
|
360
|
+
- `test-encryption-use-case.js` - Encryption verification
|
|
361
|
+
|
|
362
|
+
### 3. User Management (`/user`)
|
|
363
|
+
|
|
364
|
+
**Purpose**: Individual and organizational user authentication.
|
|
365
|
+
|
|
366
|
+
**User Types**:
|
|
367
|
+
- **Individual Users**: Personal accounts with email/password
|
|
368
|
+
- **Organization Users**: Business accounts with organization-level access
|
|
369
|
+
- **Hybrid**: Support both simultaneously
|
|
370
|
+
|
|
371
|
+
**Authentication Methods**:
|
|
372
|
+
- Password-based (bcrypt hashed)
|
|
373
|
+
- Token-based (Bearer tokens)
|
|
374
|
+
- App-based (external app user IDs)
|
|
375
|
+
|
|
376
|
+
**Use Cases**:
|
|
377
|
+
- `login-user.js` - User authentication
|
|
378
|
+
- `create-individual-user.js` - Create personal account
|
|
379
|
+
- `create-organization-user.js` - Create business account
|
|
380
|
+
- `get-user-from-bearer-token.js` - Token authentication
|
|
381
|
+
|
|
382
|
+
**Repositories**:
|
|
383
|
+
- `user-repository-factory.js` - Creates database-specific repositories
|
|
384
|
+
- `user-repository-mongo.js` - MongoDB implementation
|
|
385
|
+
- `user-repository-postgres.js` - PostgreSQL implementation
|
|
386
|
+
|
|
387
|
+
**Configuration** (in app definition):
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
{
|
|
391
|
+
user: {
|
|
392
|
+
usePassword: true, // Enable password auth
|
|
393
|
+
primary: 'individual', // Primary user type
|
|
394
|
+
individualUserRequired: true, // Require individual user
|
|
395
|
+
organizationUserRequired: false // Optional org user
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 4. Module System (`/modules`)
|
|
401
|
+
|
|
402
|
+
**Purpose**: API module loading, credential management, and HTTP clients.
|
|
403
|
+
|
|
404
|
+
**Key Classes**:
|
|
405
|
+
- `Credential` - API credentials domain entity
|
|
406
|
+
- `Entity` - External service entity (account, workspace, etc.)
|
|
407
|
+
- `Requester` - Base HTTP client class
|
|
408
|
+
- `OAuth2Requester` - OAuth 2.0 flow implementation
|
|
409
|
+
- `ApiKeyRequester` - API key authentication
|
|
410
|
+
- `BasicAuthRequester` - Basic authentication
|
|
411
|
+
|
|
412
|
+
**Module Factory**:
|
|
413
|
+
- `ModuleFactory` - Creates and configures API module instances
|
|
414
|
+
- Handles credential injection
|
|
415
|
+
- Manages module lifecycle
|
|
416
|
+
|
|
417
|
+
**Repositories**:
|
|
418
|
+
- `module-repository.js` - Module data access
|
|
419
|
+
- `credential-repository.js` - Credential persistence (encrypted)
|
|
420
|
+
|
|
421
|
+
### 5. Core Runtime System (`/core`)
|
|
422
|
+
|
|
423
|
+
**Purpose**: Lambda-optimized runtime with handlers, workers, and delegates.
|
|
424
|
+
|
|
425
|
+
**See**: `core/CLAUDE.md` for comprehensive documentation
|
|
426
|
+
|
|
427
|
+
**Key Components**:
|
|
428
|
+
- `create-handler.js` - Lambda handler factory
|
|
429
|
+
- `Worker.js` - SQS job processing base class
|
|
430
|
+
- `Delegate.js` - Observer/delegation pattern
|
|
431
|
+
- `load-installed-modules.js` - Dynamic module loading
|
|
432
|
+
|
|
433
|
+
**Handler Pattern**:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
const { createHandler } = require('@friggframework/core');
|
|
437
|
+
|
|
438
|
+
const handler = createHandler({
|
|
439
|
+
eventName: 'MyIntegration',
|
|
440
|
+
isUserFacingResponse: true, // Sanitize errors
|
|
441
|
+
shouldUseDatabase: true, // Connect to DB
|
|
442
|
+
method: async (event, context) => {
|
|
443
|
+
// Your logic here
|
|
444
|
+
return { statusCode: 200, body: 'Success' };
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Worker Pattern**:
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
const { Worker } = require('@friggframework/core');
|
|
453
|
+
|
|
454
|
+
class MyWorker extends Worker {
|
|
455
|
+
_validateParams(params) {
|
|
456
|
+
this._verifyParamExists(params, 'requiredField');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async _run(params, context) {
|
|
460
|
+
// Process SQS message
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 6. Encryption System (`/encrypt`)
|
|
466
|
+
|
|
467
|
+
**Purpose**: Cryptor adapter for AWS KMS and AES encryption.
|
|
468
|
+
|
|
469
|
+
**Key Class**: `Cryptor.js`
|
|
470
|
+
- Envelope encryption pattern
|
|
471
|
+
- AWS KMS integration
|
|
472
|
+
- AES-256-GCM fallback
|
|
473
|
+
- Key rotation support
|
|
474
|
+
|
|
475
|
+
**Usage**:
|
|
476
|
+
|
|
477
|
+
```javascript
|
|
478
|
+
const { Cryptor } = require('@friggframework/core');
|
|
479
|
+
|
|
480
|
+
const cryptor = new Cryptor({
|
|
481
|
+
shouldUseAws: process.env.KMS_KEY_ARN ? true : false
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const encrypted = await cryptor.encrypt('sensitive-data');
|
|
485
|
+
const decrypted = await cryptor.decrypt(encrypted);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### 7. Handlers & Routers (`/handlers`)
|
|
489
|
+
|
|
490
|
+
**Purpose**: HTTP/Lambda request handling and routing.
|
|
491
|
+
|
|
492
|
+
**Key Routers**:
|
|
493
|
+
- `integration-router.js` - Integration CRUD operations
|
|
494
|
+
- `auth.js` - Authentication endpoints
|
|
495
|
+
- `health.js` - Health check endpoints with encryption verification
|
|
496
|
+
|
|
497
|
+
**Handler Types**:
|
|
498
|
+
- **User-facing**: Sanitize errors, friendly responses
|
|
499
|
+
- **Server-to-server**: Full error details for debugging
|
|
500
|
+
- **Background workers**: SQS message processing
|
|
501
|
+
|
|
502
|
+
**Event Dispatcher**:
|
|
503
|
+
- `integration-event-dispatcher.js` - Routes events to integration handlers
|
|
504
|
+
- Supports lifecycle events and user actions
|
|
505
|
+
|
|
506
|
+
**Queue handler delivery**: a handler dispatched from the integration queue
|
|
507
|
+
(any `this.events` entry reached through SQS, including `ON_WEBHOOK` and
|
|
508
|
+
scheduled jobs) receives `{ data, context, delivery }`. HTTP-dispatched
|
|
509
|
+
handlers (`{ req, res, next }`) and `this.on` events do not.
|
|
510
|
+
|
|
511
|
+
| Field | Type | Value |
|
|
512
|
+
|---|---|---|
|
|
513
|
+
| `delivery.receiveCount` | `number \| undefined` | SQS `ApproximateReceiveCount` of this delivery |
|
|
514
|
+
| `delivery.maxReceiveCount` | `number \| undefined` | Receives allowed before SQS moves the message to the DLQ (`FRIGG_QUEUE_MAX_RECEIVE_COUNT`) |
|
|
515
|
+
| `delivery.isLastAttempt` | `boolean` | `true` only when both counts are known and `receiveCount >= maxReceiveCount` |
|
|
516
|
+
|
|
517
|
+
`isLastAttempt` is `false` when either count is unknown: a local or non-SQS
|
|
518
|
+
invocation, or a queue whose redrive policy the stack does not own
|
|
519
|
+
(`ownership.queue: 'external'`). The value is information only: core still
|
|
520
|
+
rethrows retryable errors and discards halt errors (4xx except 408/429).
|
|
521
|
+
|
|
522
|
+
Use it to end a run or count lost work on the final try: when a retryable
|
|
523
|
+
error (429, 5xx, network) is about to be rethrown and `isLastAttempt` is
|
|
524
|
+
`true`, the message goes to the DLQ next, so mark the run failed or count the
|
|
525
|
+
message's records as failed before rethrowing.
|
|
526
|
+
|
|
527
|
+
```javascript
|
|
528
|
+
async processBatch({ data, delivery }) {
|
|
529
|
+
try {
|
|
530
|
+
await this.syncPage(data);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (delivery?.isLastAttempt) await this.failRun(data.processId, error);
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The single source of the max receive count is
|
|
539
|
+
`INTEGRATION_QUEUE_MAX_RECEIVE_COUNT` in `queues/queue-delivery.js`. The
|
|
540
|
+
devtools integration builder uses it for the queue's `RedrivePolicy` and sets
|
|
541
|
+
it as `FRIGG_QUEUE_MAX_RECEIVE_COUNT` on the queue worker function.
|
|
542
|
+
|
|
543
|
+
### 8. Error Handling (`/errors`)
|
|
544
|
+
|
|
545
|
+
**Purpose**: Standardized error types with proper HTTP semantics.
|
|
546
|
+
|
|
547
|
+
**Error Types**:
|
|
548
|
+
- `BaseError` - Base error class
|
|
549
|
+
- `FetchError` - HTTP request failures
|
|
550
|
+
- `HaltError` - Stop processing without retry
|
|
551
|
+
- `RequiredPropertyError` - Missing required parameters
|
|
552
|
+
- `ParameterTypeError` - Invalid parameter type
|
|
553
|
+
|
|
554
|
+
**Usage**:
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
const { RequiredPropertyError } = require('@friggframework/core');
|
|
558
|
+
|
|
559
|
+
if (!userId) {
|
|
560
|
+
throw new RequiredPropertyError('userId is required');
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 9. Logging System (`/logs`)
|
|
565
|
+
|
|
566
|
+
**Purpose**: One redacted JSON record per line to stdout (ADR-048). See `docs/guides/LOGGING.md`.
|
|
567
|
+
|
|
568
|
+
**Rules**:
|
|
569
|
+
- `logs/` is a leaf: it requires only Node built-ins and sibling `logs/` files.
|
|
570
|
+
- Integrations and API modules use `this.logger`; core uses one `getLogger('frigg.<area>')` per module.
|
|
571
|
+
- Fixed message text; ids and counts go into fields. `frigg.*` records at WARN and above need an `eventName` (`<logger name>.<action>`); tests fail without it.
|
|
572
|
+
- Log an error one time, at the boundary (`createHandler`, the express middleware, `Worker.run`, the DLQ processor). Inner code throws with `cause`.
|
|
573
|
+
- Use `statusCode`, not `status` (reserved key). `body`, `payload`, `response` are dropped at INFO and above.
|
|
574
|
+
- Tests assert on `createMemorySink()` records by `eventName`, not on console spies.
|
|
575
|
+
- `debug`, `initDebugLog`, `flushDebugLog` are deprecated shims.
|
|
576
|
+
|
|
577
|
+
**Usage**:
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
const { getLogger } = require('../logs');
|
|
581
|
+
const log = getLogger('frigg.core.sync');
|
|
582
|
+
|
|
583
|
+
log.warn('Sync skipped', { eventName: 'frigg.core.sync.skipped', processId });
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### 10. Lambda Utilities (`/lambda`)
|
|
587
|
+
|
|
588
|
+
**Purpose**: AWS Lambda-specific utilities.
|
|
589
|
+
|
|
590
|
+
**Key Classes**:
|
|
591
|
+
- `TimeoutCatcher` - Detect approaching Lambda timeout
|
|
592
|
+
- Graceful shutdown handling
|
|
593
|
+
|
|
594
|
+
**Usage**:
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
const { TimeoutCatcher } = require('@friggframework/core');
|
|
598
|
+
|
|
599
|
+
exports.handler = async (event, context) => {
|
|
600
|
+
const timeoutCatcher = new TimeoutCatcher(context);
|
|
601
|
+
|
|
602
|
+
if (timeoutCatcher.isNearTimeout()) {
|
|
603
|
+
// Save state and exit gracefully
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Development Workflow
|
|
609
|
+
|
|
610
|
+
### Adding a New Use Case
|
|
611
|
+
|
|
612
|
+
1. **Create use case file** in appropriate `use-cases/` directory:
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
// integrations/use-cases/my-new-use-case.js
|
|
616
|
+
class MyNewUseCase {
|
|
617
|
+
constructor({ integrationRepository, userRepository }) {
|
|
618
|
+
this.integrationRepo = integrationRepository;
|
|
619
|
+
this.userRepo = userRepository;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async execute(userId, integrationId) {
|
|
623
|
+
// Business logic here
|
|
624
|
+
const user = await this.userRepo.findById(userId);
|
|
625
|
+
const integration = await this.integrationRepo.findById(integrationId);
|
|
626
|
+
|
|
627
|
+
// Validate, orchestrate, coordinate
|
|
628
|
+
// Return result
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
module.exports = { MyNewUseCase };
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
2. **Add tests** in corresponding `tests/` directory
|
|
636
|
+
3. **Export** from parent index.js if needed
|
|
637
|
+
4. **Use in handler** - handlers call use cases, not repositories
|
|
638
|
+
|
|
639
|
+
### Adding Encrypted Fields
|
|
640
|
+
|
|
641
|
+
**For custom models** (integration developers):
|
|
642
|
+
|
|
643
|
+
In `backend/index.js`:
|
|
644
|
+
|
|
645
|
+
```javascript
|
|
646
|
+
const appDefinition = {
|
|
647
|
+
encryption: {
|
|
648
|
+
schema: {
|
|
649
|
+
MyCustomModel: {
|
|
650
|
+
fields: ['secretData', 'data.apiKey']
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**For core models** (framework developers):
|
|
658
|
+
|
|
659
|
+
Edit `database/encryption/encryption-schema-registry.js`:
|
|
660
|
+
|
|
661
|
+
```javascript
|
|
662
|
+
const ENCRYPTION_SCHEMA = {
|
|
663
|
+
MyModel: {
|
|
664
|
+
fields: ['sensitiveField']
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Database Migrations
|
|
670
|
+
|
|
671
|
+
**MongoDB** (Prisma push):
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
npm run prisma:push:mongo
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
**PostgreSQL** (Prisma migrate):
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
npm run prisma:migrate:postgres
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Integration Development
|
|
684
|
+
|
|
685
|
+
1. **Extend IntegrationBase** in your app
|
|
686
|
+
2. **Define static Definition** with name, version, modules
|
|
687
|
+
3. **Implement lifecycle methods**: `onCreate`, `onUpdate`, `onDelete`
|
|
688
|
+
4. **Add event handlers** for webhooks and user actions
|
|
689
|
+
5. **Use framework services**: repositories, encryption, logging
|
|
690
|
+
|
|
691
|
+
## Testing Strategy
|
|
692
|
+
|
|
693
|
+
### Test Categories
|
|
694
|
+
|
|
695
|
+
1. **Unit Tests**: Use cases with mocked repositories
|
|
696
|
+
2. **Integration Tests**: Full flow with real dependencies
|
|
697
|
+
3. **Repository Tests**: Database operations
|
|
698
|
+
4. **Handler Tests**: HTTP/Lambda response testing
|
|
699
|
+
|
|
700
|
+
### Test Structure
|
|
701
|
+
|
|
702
|
+
```javascript
|
|
703
|
+
describe('MyUseCase', () => {
|
|
704
|
+
let useCase;
|
|
705
|
+
let mockRepository;
|
|
706
|
+
|
|
707
|
+
beforeEach(() => {
|
|
708
|
+
mockRepository = {
|
|
709
|
+
findById: jest.fn(),
|
|
710
|
+
save: jest.fn()
|
|
711
|
+
};
|
|
712
|
+
useCase = new MyUseCase({ repository: mockRepository });
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('executes successfully', async () => {
|
|
716
|
+
mockRepository.findById.mockResolvedValue({ id: '123' });
|
|
717
|
+
|
|
718
|
+
const result = await useCase.execute('123');
|
|
719
|
+
|
|
720
|
+
expect(result).toBeDefined();
|
|
721
|
+
expect(mockRepository.findById).toHaveBeenCalledWith('123');
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Running Tests
|
|
727
|
+
|
|
728
|
+
```bash
|
|
729
|
+
# All tests
|
|
730
|
+
npm test
|
|
731
|
+
|
|
732
|
+
# Specific file
|
|
733
|
+
npm test -- path/to/test.test.js
|
|
734
|
+
|
|
735
|
+
# Pattern matching
|
|
736
|
+
npm test -- --testPathPattern="encryption"
|
|
737
|
+
|
|
738
|
+
# With coverage
|
|
739
|
+
npm test -- --coverage
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### Test Doubles
|
|
743
|
+
|
|
744
|
+
Use test doubles from `@friggframework/test` package for consistent mocking.
|
|
745
|
+
|
|
746
|
+
## Anti-Patterns
|
|
747
|
+
|
|
748
|
+
### Architecture Anti-Patterns
|
|
749
|
+
|
|
750
|
+
❌ **Don't call repositories from handlers** - Always use use cases
|
|
751
|
+
❌ **Don't put business logic in repositories** - Repositories are pure data access
|
|
752
|
+
❌ **Don't put HTTP concerns in use cases** - Use cases are protocol-agnostic
|
|
753
|
+
❌ **Don't bypass encryption** - Sensitive data must be encrypted
|
|
754
|
+
❌ **Don't expose internal errors to users** - Use `isUserFacingResponse: true`
|
|
755
|
+
❌ **Don't skip connection pooling** - Set `callbackWaitsForEmptyEventLoop = false`
|
|
756
|
+
|
|
757
|
+
### Development Anti-Patterns
|
|
758
|
+
|
|
759
|
+
❌ **Don't modify node_modules** - Extend through proper patterns
|
|
760
|
+
❌ **Don't hardcode credentials** - Use environment variables
|
|
761
|
+
❌ **Don't skip tests** - Maintain test coverage
|
|
762
|
+
❌ **Don't commit secrets** - Use .gitignore and AWS Secrets Manager
|
|
763
|
+
❌ **Don't ignore linting errors** - Run `npm run lint:fix`
|
|
764
|
+
|
|
765
|
+
### Integration Development Anti-Patterns
|
|
766
|
+
|
|
767
|
+
❌ **Don't bypass IntegrationBase** - Always extend the base class
|
|
768
|
+
❌ **Don't ignore lifecycle methods** - Implement onCreate, onUpdate, onDelete
|
|
769
|
+
❌ **Don't skip signature validation** - Validate all webhooks
|
|
770
|
+
❌ **Don't sync operations in handlers** - Use background workers for long tasks
|
|
771
|
+
❌ **Don't ignore errors** - Proper error handling and logging
|
|
772
|
+
|
|
773
|
+
## Environment Variables
|
|
774
|
+
|
|
775
|
+
### Required
|
|
776
|
+
|
|
777
|
+
- `AWS_REGION` - AWS region for services
|
|
778
|
+
- `DATABASE_URL` - Database connection string (auto-set)
|
|
779
|
+
- `DB_TYPE` - Database type: 'mongodb' or 'postgresql'
|
|
780
|
+
|
|
781
|
+
### Encryption
|
|
782
|
+
|
|
783
|
+
- `KMS_KEY_ARN` - AWS KMS key ARN (production)
|
|
784
|
+
- `AES_KEY_ID` - AES key ID (development)
|
|
785
|
+
- `AES_KEY` - AES encryption key (development)
|
|
786
|
+
- `STAGE` - Environment stage (dev, test, local bypass encryption)
|
|
787
|
+
|
|
788
|
+
### Optional
|
|
789
|
+
|
|
790
|
+
- `SECRET_ARN` - AWS Secrets Manager ARN for auto-injection
|
|
791
|
+
- `FRIGG_LOG_LEVEL` - Minimum log level (`TRACE` … `FATAL`, default `INFO`; `DEBUG` on local runs)
|
|
792
|
+
- `FRIGG_QUEUE_MAX_RECEIVE_COUNT` - Set by devtools on integration queue workers whose queue the stack owns; feeds `delivery.maxReceiveCount`
|
|
793
|
+
|
|
794
|
+
## Version Information
|
|
795
|
+
|
|
796
|
+
- **Current Version**: 2.0.0-next.0 (pre-release)
|
|
797
|
+
- **Node.js**: >=18 required
|
|
798
|
+
- **Dependencies**: See package.json for full list
|
|
799
|
+
|
|
800
|
+
## Support and Documentation
|
|
801
|
+
|
|
802
|
+
- **Main Framework CLAUDE.md**: See root Frigg CLAUDE.md for framework-wide guidance
|
|
803
|
+
- **Core Runtime**: See `core/CLAUDE.md` for Lambda/Worker patterns
|
|
804
|
+
- **Encryption**: See `database/encryption/README.md` for encryption details
|
|
805
|
+
- **Package README**: See `README.md` for API reference
|
|
806
|
+
|
|
807
|
+
## Recent Important Changes
|
|
808
|
+
|
|
809
|
+
### Field-Level Encryption JSON Object Support
|
|
810
|
+
|
|
811
|
+
**Date**: 2025-01-06
|
|
812
|
+
|
|
813
|
+
**Problem**: The `FieldEncryptionService` was converting objects to the string `"[object Object]"` before encrypting, corrupting JSON fields like `IntegrationMapping.mapping`.
|
|
814
|
+
|
|
815
|
+
**Solution**: Added `_serializeForEncryption()` and `_deserializeAfterDecryption()` methods:
|
|
816
|
+
- Objects are now JSON.stringify'd before encryption
|
|
817
|
+
- Decrypted strings are JSON.parse'd back to objects
|
|
818
|
+
- Plain strings work as before
|
|
819
|
+
|
|
820
|
+
**Files Changed**:
|
|
821
|
+
- `database/encryption/field-encryption-service.js`
|
|
822
|
+
- `database/encryption/field-encryption-service.test.js`
|
|
823
|
+
|
|
824
|
+
**Test Coverage**: All 40 tests pass, including new object encryption test.
|
|
825
|
+
|
|
826
|
+
**Impact**: `IntegrationMapping.mapping` and other JSON fields now correctly round-trip through encryption.
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
**Built with ❤️ by the Frigg Framework team**
|