@friggframework/core 2.0.0-next.8 → 2.0.0-next.81
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/CLAUDE.md +702 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +73 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +60 -24
- package/core/create-handler.js +79 -8
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +287 -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 +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +222 -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/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 +477 -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 +15 -7
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +335 -0
- package/generated/prisma-mongodb/index-browser.js +317 -0
- package/generated/prisma-mongodb/index.d.ts +22955 -0
- package/generated/prisma-mongodb/index.js +360 -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 +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/library.js +146 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +368 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +342 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +357 -0
- package/generated/prisma-postgresql/index-browser.js +339 -0
- package/generated/prisma-postgresql/index.d.ts +25135 -0
- package/generated/prisma-postgresql/index.js +382 -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 +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/library.js +146 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +351 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +364 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +57 -0
- package/handlers/backend-utils.js +262 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +516 -0
- package/handlers/routers/integration-defined-routers.js +45 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- 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 +352 -0
- package/handlers/workers/dlq-processor.js +63 -0
- package/handlers/workers/integration-defined-workers.js +23 -0
- package/index.js +82 -46
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -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/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +364 -55
- package/integrations/integration-router.js +376 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +219 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +144 -0
- package/integrations/repositories/integration-repository-mongo.js +330 -0
- package/integrations/repositories/integration-repository-postgres.js +385 -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/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +112 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -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 +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/load-integration-context.js +71 -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 +205 -0
- package/integrations/use-cases/update-process-state.js +158 -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/logs/logger.js +0 -4
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +258 -0
- package/modules/repositories/module-repository-documentdb.js +335 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +408 -0
- package/modules/repositories/module-repository-postgres.js +453 -0
- package/modules/repositories/module-repository.js +345 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +396 -0
- package/modules/requester/requester.js +275 -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 +177 -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 +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +368 -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/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +351 -0
- package/queues/queuer-util.js +103 -21
- 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/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/integrations/index.d.ts +3 -8
- package/types/module-plugin/index.d.ts +17 -69
- package/types/syncs/index.d.ts +0 -17
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -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 +77 -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/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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateProcessMetrics Use Case
|
|
3
|
+
*
|
|
4
|
+
* Updates process metrics atomically via
|
|
5
|
+
* `processRepository.applyProcessUpdate`. This is the race-safe
|
|
6
|
+
* replacement for the original read-modify-write implementation — the
|
|
7
|
+
* long-standing TODO about lost updates under concurrent writers is
|
|
8
|
+
* now resolved.
|
|
9
|
+
*
|
|
10
|
+
* Split into two phases:
|
|
11
|
+
*
|
|
12
|
+
* 1. Atomic phase — counters and bounded error history. Uses
|
|
13
|
+
* $inc / $push+$slice (Mongo/DocumentDB) or jsonb_set with
|
|
14
|
+
* arithmetic expressions (Postgres) in a single UPDATE ... RETURNING
|
|
15
|
+
* so concurrent callers serialize at the DB layer.
|
|
16
|
+
*
|
|
17
|
+
* 2. Derived-fields phase — duration, recordsPerSecond,
|
|
18
|
+
* estimatedCompletion. Computed from the post-atomic snapshot and
|
|
19
|
+
* written via the legacy (non-atomic) `update()` method.
|
|
20
|
+
* Intentionally best-effort: under concurrent writers they reflect
|
|
21
|
+
* "whichever handler wrote last" — the same semantics they had
|
|
22
|
+
* before and all they've ever guaranteed. Preserved for backward
|
|
23
|
+
* compatibility with consumers (UI, WebSocket listeners).
|
|
24
|
+
*
|
|
25
|
+
* Optionally broadcasts progress via WebSocket service if provided.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const updateMetrics = new UpdateProcessMetrics({ processRepository, websocketService });
|
|
29
|
+
* await updateMetrics.execute(processId, {
|
|
30
|
+
* processed: 100,
|
|
31
|
+
* success: 95,
|
|
32
|
+
* errors: 5,
|
|
33
|
+
* errorDetails: [{ contactId: 'abc', error: 'Missing email', timestamp: '...' }]
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
class UpdateProcessMetrics {
|
|
37
|
+
/**
|
|
38
|
+
* @param {Object} params
|
|
39
|
+
* @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
|
|
40
|
+
* @param {Object} [params.websocketService] - Optional WebSocket service for progress broadcasting
|
|
41
|
+
*/
|
|
42
|
+
constructor({ processRepository, websocketService }) {
|
|
43
|
+
if (!processRepository) {
|
|
44
|
+
throw new Error('processRepository is required');
|
|
45
|
+
}
|
|
46
|
+
this.processRepository = processRepository;
|
|
47
|
+
this.websocketService = websocketService;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute the use case to update process metrics
|
|
52
|
+
* @param {string} processId - Process ID to update
|
|
53
|
+
* @param {Object} metricsUpdate - Metrics to add/update
|
|
54
|
+
* @param {number} [metricsUpdate.processed=0] - Records processed in this batch
|
|
55
|
+
* @param {number} [metricsUpdate.success=0] - Successful records
|
|
56
|
+
* @param {number} [metricsUpdate.errors=0] - Failed records
|
|
57
|
+
* @param {Array} [metricsUpdate.errorDetails=[]] - Error details array
|
|
58
|
+
* @returns {Promise<Object>} Updated process record
|
|
59
|
+
* @throws {Error} If process not found or update fails
|
|
60
|
+
*/
|
|
61
|
+
async execute(processId, metricsUpdate) {
|
|
62
|
+
if (!processId || typeof processId !== 'string') {
|
|
63
|
+
throw new Error('processId must be a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
if (!metricsUpdate || typeof metricsUpdate !== 'object') {
|
|
66
|
+
throw new Error('metricsUpdate must be an object');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Phase 1: atomic increments + bounded error history.
|
|
70
|
+
const increment = {};
|
|
71
|
+
const processed = metricsUpdate.processed || 0;
|
|
72
|
+
const success = metricsUpdate.success || 0;
|
|
73
|
+
const errors = metricsUpdate.errors || 0;
|
|
74
|
+
if (processed) increment['context.processedRecords'] = processed;
|
|
75
|
+
if (success) increment['results.aggregateData.totalSynced'] = success;
|
|
76
|
+
if (errors) increment['results.aggregateData.totalFailed'] = errors;
|
|
77
|
+
|
|
78
|
+
const pushSlice = {};
|
|
79
|
+
if (
|
|
80
|
+
Array.isArray(metricsUpdate.errorDetails) &&
|
|
81
|
+
metricsUpdate.errorDetails.length > 0
|
|
82
|
+
) {
|
|
83
|
+
pushSlice['results.aggregateData.errors'] = {
|
|
84
|
+
values: metricsUpdate.errorDetails,
|
|
85
|
+
keepLast: 100,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const hasAtomicWork =
|
|
90
|
+
Object.keys(increment).length > 0 ||
|
|
91
|
+
Object.keys(pushSlice).length > 0;
|
|
92
|
+
|
|
93
|
+
let updatedProcess;
|
|
94
|
+
try {
|
|
95
|
+
if (hasAtomicWork) {
|
|
96
|
+
updatedProcess = await this.processRepository.applyProcessUpdate(
|
|
97
|
+
processId,
|
|
98
|
+
{ increment, pushSlice }
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
// All-zero update (e.g., empty batch) — nothing to persist;
|
|
102
|
+
// just read current state for the derived-fields pass.
|
|
103
|
+
updatedProcess = await this.processRepository.findById(
|
|
104
|
+
processId
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Failed to update process metrics: ${error.message}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!updatedProcess) {
|
|
114
|
+
throw new Error(`Process not found: ${processId}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Phase 2: derived metrics (non-atomic, best-effort). Preserved
|
|
118
|
+
// for backward compatibility — these were always stale under
|
|
119
|
+
// concurrent writers even before this refactor.
|
|
120
|
+
const context = updatedProcess.context || {};
|
|
121
|
+
const results = updatedProcess.results || { aggregateData: {} };
|
|
122
|
+
if (!results.aggregateData) results.aggregateData = {};
|
|
123
|
+
|
|
124
|
+
if (context.processedRecords > 0 || context.totalRecords > 0) {
|
|
125
|
+
const startTime = new Date(
|
|
126
|
+
context.startTime || updatedProcess.createdAt
|
|
127
|
+
);
|
|
128
|
+
const elapsed = Date.now() - startTime.getTime();
|
|
129
|
+
results.aggregateData.duration = elapsed;
|
|
130
|
+
|
|
131
|
+
if (elapsed > 0 && context.processedRecords > 0) {
|
|
132
|
+
results.aggregateData.recordsPerSecond =
|
|
133
|
+
context.processedRecords / (elapsed / 1000);
|
|
134
|
+
} else {
|
|
135
|
+
results.aggregateData.recordsPerSecond = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (context.totalRecords > 0 && context.processedRecords > 0) {
|
|
139
|
+
const remaining =
|
|
140
|
+
context.totalRecords - context.processedRecords;
|
|
141
|
+
if (results.aggregateData.recordsPerSecond > 0) {
|
|
142
|
+
const etaMs =
|
|
143
|
+
(remaining / results.aggregateData.recordsPerSecond) *
|
|
144
|
+
1000;
|
|
145
|
+
const eta = new Date(Date.now() + etaMs);
|
|
146
|
+
context.estimatedCompletion = eta.toISOString();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
updatedProcess = await this.processRepository.update(
|
|
152
|
+
processId,
|
|
153
|
+
{ context, results }
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Derived-field write failures are NON-FATAL — atomic
|
|
157
|
+
// counters from phase 1 already landed. Log and return the
|
|
158
|
+
// post-atomic snapshot.
|
|
159
|
+
console.error(
|
|
160
|
+
'[UpdateProcessMetrics] derived-fields write failed (non-fatal):',
|
|
161
|
+
error.message
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (this.websocketService) {
|
|
167
|
+
await this._broadcastProgress(updatedProcess);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return updatedProcess;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Broadcast progress update via WebSocket
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
async _broadcastProgress(process) {
|
|
178
|
+
try {
|
|
179
|
+
const context = process.context || {};
|
|
180
|
+
const results = process.results || { aggregateData: {} };
|
|
181
|
+
const aggregateData = results.aggregateData || {};
|
|
182
|
+
|
|
183
|
+
await this.websocketService.broadcast({
|
|
184
|
+
type: 'PROCESS_PROGRESS',
|
|
185
|
+
data: {
|
|
186
|
+
processId: process.id,
|
|
187
|
+
processName: process.name,
|
|
188
|
+
processType: process.type,
|
|
189
|
+
state: process.state,
|
|
190
|
+
processed: context.processedRecords || 0,
|
|
191
|
+
total: context.totalRecords || 0,
|
|
192
|
+
successCount: aggregateData.totalSynced || 0,
|
|
193
|
+
errorCount: aggregateData.totalFailed || 0,
|
|
194
|
+
recordsPerSecond: aggregateData.recordsPerSecond || 0,
|
|
195
|
+
estimatedCompletion: context.estimatedCompletion || null,
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('Failed to broadcast process progress:', error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { UpdateProcessMetrics };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateProcessState Use Case
|
|
3
|
+
*
|
|
4
|
+
* Updates the state of a process and optionally merges context updates.
|
|
5
|
+
* Handles state transitions in the process state machine.
|
|
6
|
+
*
|
|
7
|
+
* Design Philosophy:
|
|
8
|
+
* - State transitions are explicit and tracked
|
|
9
|
+
* - Context updates are merged (not replaced) to preserve data
|
|
10
|
+
* - Repository handles persistence, use case handles business logic
|
|
11
|
+
*
|
|
12
|
+
* State Machine (CRM Sync Example):
|
|
13
|
+
* INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
|
|
14
|
+
* COMPLETING → COMPLETED
|
|
15
|
+
*
|
|
16
|
+
* Any state can transition to ERROR on failure.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const updateProcessState = new UpdateProcessState({ processRepository });
|
|
20
|
+
* await updateProcessState.execute(processId, 'FETCHING_TOTAL', {
|
|
21
|
+
* currentPage: 1,
|
|
22
|
+
* pagination: { pageSize: 100 }
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
class UpdateProcessState {
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} params
|
|
28
|
+
* @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
|
|
29
|
+
*/
|
|
30
|
+
constructor({ processRepository }) {
|
|
31
|
+
if (!processRepository) {
|
|
32
|
+
throw new Error('processRepository is required');
|
|
33
|
+
}
|
|
34
|
+
this.processRepository = processRepository;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute the use case to update process state
|
|
39
|
+
* @param {string} processId - Process ID to update
|
|
40
|
+
* @param {string} newState - New state value
|
|
41
|
+
* @param {Object} [contextUpdates={}] - Context fields to merge
|
|
42
|
+
* @returns {Promise<Object>} Updated process record
|
|
43
|
+
* @throws {Error} If process not found or update fails
|
|
44
|
+
*/
|
|
45
|
+
async execute(processId, newState, contextUpdates = {}) {
|
|
46
|
+
// Validate inputs
|
|
47
|
+
if (!processId || typeof processId !== 'string') {
|
|
48
|
+
throw new Error('processId must be a non-empty string');
|
|
49
|
+
}
|
|
50
|
+
if (!newState || typeof newState !== 'string') {
|
|
51
|
+
throw new Error('newState must be a non-empty string');
|
|
52
|
+
}
|
|
53
|
+
if (contextUpdates && typeof contextUpdates !== 'object') {
|
|
54
|
+
throw new Error('contextUpdates must be an object');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Route through the atomic path when the repo supports it AND we
|
|
58
|
+
// have context keys to set. The atomic path writes the state
|
|
59
|
+
// column + the context field-sets in one DB round trip without
|
|
60
|
+
// read-modify-write, so a concurrent counter bump from
|
|
61
|
+
// UpdateProcessMetrics can't clobber our flags (e.g. `fetchDone`)
|
|
62
|
+
// or vice versa.
|
|
63
|
+
//
|
|
64
|
+
// Each context update key becomes a `set` at path
|
|
65
|
+
// `context.<key>` — matching the prior semantics of a shallow
|
|
66
|
+
// top-level merge (sub-objects were and still are replaced
|
|
67
|
+
// whole, not deep-merged).
|
|
68
|
+
const hasContextKeys =
|
|
69
|
+
contextUpdates && Object.keys(contextUpdates).length > 0;
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
hasContextKeys &&
|
|
73
|
+
typeof this.processRepository.applyProcessUpdate === 'function'
|
|
74
|
+
) {
|
|
75
|
+
const set = {};
|
|
76
|
+
for (const [key, value] of Object.entries(contextUpdates)) {
|
|
77
|
+
set[`context.${key}`] = value;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const updated = await this.processRepository.applyProcessUpdate(
|
|
81
|
+
processId,
|
|
82
|
+
{ set, newState }
|
|
83
|
+
);
|
|
84
|
+
if (!updated) {
|
|
85
|
+
throw new Error(`Process not found: ${processId}`);
|
|
86
|
+
}
|
|
87
|
+
return updated;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Failed to update process state: ${error.message}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Legacy path (no contextUpdates or repo lacks applyProcessUpdate):
|
|
96
|
+
// preserve the original read-merge-write semantics for backward
|
|
97
|
+
// compatibility with any custom repos. Wrap the full read+write
|
|
98
|
+
// in try/catch so a findById error surfaces under the same
|
|
99
|
+
// "Failed to update process state" message as a write failure.
|
|
100
|
+
try {
|
|
101
|
+
const process = await this.processRepository.findById(processId);
|
|
102
|
+
if (!process) {
|
|
103
|
+
throw new Error(`Process not found: ${processId}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const updates = { state: newState };
|
|
107
|
+
if (hasContextKeys) {
|
|
108
|
+
updates.context = {
|
|
109
|
+
...process.context,
|
|
110
|
+
...contextUpdates,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return await this.processRepository.update(processId, updates);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// Re-throw "Process not found" as-is; wrap other errors.
|
|
117
|
+
if (error.message && error.message.startsWith('Process not found')) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Failed to update process state: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Helper method to update state without context changes
|
|
126
|
+
* @param {string} processId - Process ID to update
|
|
127
|
+
* @param {string} newState - New state value
|
|
128
|
+
* @returns {Promise<Object>} Updated process record
|
|
129
|
+
*/
|
|
130
|
+
async updateStateOnly(processId, newState) {
|
|
131
|
+
return this.execute(processId, newState, {});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Helper method to update context without changing state
|
|
136
|
+
* @param {string} processId - Process ID to update
|
|
137
|
+
* @param {Object} contextUpdates - Context fields to merge
|
|
138
|
+
* @returns {Promise<Object>} Updated process record
|
|
139
|
+
*/
|
|
140
|
+
async updateContextOnly(processId, contextUpdates) {
|
|
141
|
+
const process = await this.processRepository.findById(processId);
|
|
142
|
+
if (!process) {
|
|
143
|
+
throw new Error(`Process not found: ${processId}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const updates = {
|
|
147
|
+
context: {
|
|
148
|
+
...process.context,
|
|
149
|
+
...contextUpdates,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return this.processRepository.update(processId, updates);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { UpdateProcessState };
|
|
158
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('../integration').Integration} integration
|
|
3
|
+
* Convert an Integration domain instance to a plain DTO suitable for JSON responses.
|
|
4
|
+
* Can also accept a plain object with an 'options' property to avoid unnecessary instantiation.
|
|
5
|
+
*/
|
|
6
|
+
function mapIntegrationClassToIntegrationDTO(integration) {
|
|
7
|
+
if (!integration) return null;
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
id: integration.id,
|
|
11
|
+
userId: integration.userId,
|
|
12
|
+
entities: integration.entities,
|
|
13
|
+
config: integration.config,
|
|
14
|
+
status: integration.status,
|
|
15
|
+
version: integration.version,
|
|
16
|
+
messages: integration.messages,
|
|
17
|
+
userActions: integration.userActions,
|
|
18
|
+
options: integration.options || (typeof integration.getOptionDetails === 'function' ? integration.getOptionDetails() : null),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
|
|
24
|
+
return [
|
|
25
|
+
...new Set(
|
|
26
|
+
integrationClasses
|
|
27
|
+
.map((integration) =>
|
|
28
|
+
Object.values(integration.Definition.modules).map(
|
|
29
|
+
(module) => module.definition
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
.flat()
|
|
33
|
+
),
|
|
34
|
+
];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
|
package/logs/logger.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const util = require('util');
|
|
2
|
-
const aws = require('aws-sdk');
|
|
3
2
|
|
|
4
3
|
// Except in some outlier circumstances, for example steam or event error handlers, this should be the only place that calls `console.*`. That way, this file can be modified to log everything properly on a variety of platforms because all the logging code is here in one place.
|
|
5
4
|
/* eslint-disable no-console */
|
|
@@ -7,9 +6,6 @@ const aws = require('aws-sdk');
|
|
|
7
6
|
const logs = [];
|
|
8
7
|
let flushCalled = false;
|
|
9
8
|
|
|
10
|
-
// Log AWS SDK calls
|
|
11
|
-
aws.config.logger = { log: debug };
|
|
12
|
-
|
|
13
9
|
function debug(...messages) {
|
|
14
10
|
if (messages.length) {
|
|
15
11
|
const date = new Date();
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
const { Credential } = require('./credential');
|
|
2
|
-
const { EntityManager } = require('./entity-manager');
|
|
3
|
-
const { Entity } = require('./entity');
|
|
4
|
-
const { ModuleManager } = require('./manager');
|
|
5
1
|
const { ApiKeyRequester } = require('./requester/api-key');
|
|
6
2
|
const { BasicAuthRequester } = require('./requester/basic');
|
|
7
3
|
const { OAuth2Requester } = require('./requester/oauth-2');
|
|
8
4
|
const { Requester } = require('./requester/requester');
|
|
9
5
|
const { ModuleConstants } = require('./ModuleConstants');
|
|
10
6
|
const { ModuleFactory } = require('./module-factory');
|
|
11
|
-
const { Auther } = require('./auther');
|
|
12
7
|
|
|
13
8
|
module.exports = {
|
|
14
|
-
Credential,
|
|
15
|
-
EntityManager,
|
|
16
|
-
Entity,
|
|
17
|
-
ModuleManager,
|
|
18
9
|
ApiKeyRequester,
|
|
19
10
|
BasicAuthRequester,
|
|
20
11
|
OAuth2Requester,
|
|
21
12
|
Requester,
|
|
22
13
|
ModuleConstants,
|
|
23
14
|
ModuleFactory,
|
|
24
|
-
Auther
|
|
25
15
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// todo: remove this file
|
|
2
|
+
|
|
3
|
+
const { Module } = require('./module');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Acts as a factory for fully-hydrated domain Module instances.
|
|
7
|
+
* Provides methods to retrieve and construct Module objects with their associated
|
|
8
|
+
* entity and definition.
|
|
9
|
+
*/
|
|
10
|
+
class ModuleFactory {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} params - Configuration parameters.
|
|
13
|
+
* @param {import('./repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
|
|
14
|
+
* @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
|
|
15
|
+
*/
|
|
16
|
+
constructor({ moduleRepository, moduleDefinitions }) {
|
|
17
|
+
this.moduleRepository = moduleRepository;
|
|
18
|
+
this.moduleDefinitions = moduleDefinitions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getModuleInstance(entityId, userId) {
|
|
22
|
+
const entity = await this.moduleRepository.findEntityById(
|
|
23
|
+
entityId,
|
|
24
|
+
userId
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (!entity) {
|
|
28
|
+
throw new Error(`Entity ${entityId} not found`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (entity.userId !== userId) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Entity ${entityId} does not belong to user ${userId}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const moduleName = entity.moduleName;
|
|
38
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
39
|
+
return moduleName === def.moduleName;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!moduleDefinition) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Module definition not found for module: ${moduleName}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Module({
|
|
49
|
+
userId,
|
|
50
|
+
entity,
|
|
51
|
+
definition: moduleDefinition,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { ModuleFactory };
|