@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,380 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
ProcessRepositoryInterface,
|
|
4
|
+
} = require('./process-repository-interface');
|
|
5
|
+
const { validateOps, splitPath } = require('./process-update-ops-shared');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PostgreSQL Process Repository Adapter
|
|
9
|
+
* Handles process persistence using Prisma with PostgreSQL
|
|
10
|
+
*
|
|
11
|
+
* PostgreSQL-specific characteristics:
|
|
12
|
+
* - Uses foreign key constraints for relations
|
|
13
|
+
* - JSONB type for context and results (efficient querying)
|
|
14
|
+
* - Array type for childProcesses references
|
|
15
|
+
* - Transactional support available if needed
|
|
16
|
+
*
|
|
17
|
+
* Design Philosophy:
|
|
18
|
+
* - Same interface as MongoDB repository
|
|
19
|
+
* - Prisma abstracts away most database-specific details
|
|
20
|
+
* - Minor differences in JSON handling internally managed by Prisma
|
|
21
|
+
*/
|
|
22
|
+
class ProcessRepositoryPostgres extends ProcessRepositoryInterface {
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this.prisma = prisma;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert string ID to integer for PostgreSQL queries
|
|
30
|
+
* @private
|
|
31
|
+
* @param {string|number|null|undefined} id - ID to convert
|
|
32
|
+
* @returns {number|null|undefined} Integer ID or null/undefined
|
|
33
|
+
* @throws {Error} If ID cannot be converted to integer
|
|
34
|
+
*/
|
|
35
|
+
_convertId(id) {
|
|
36
|
+
if (id === null || id === undefined) return id;
|
|
37
|
+
const parsed = parseInt(id, 10);
|
|
38
|
+
if (isNaN(parsed)) {
|
|
39
|
+
throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a new process record
|
|
46
|
+
* @param {Object} processData - Process data to create
|
|
47
|
+
* @returns {Promise<Object>} Created process record
|
|
48
|
+
*/
|
|
49
|
+
async create(processData) {
|
|
50
|
+
const process = await this.prisma.process.create({
|
|
51
|
+
data: {
|
|
52
|
+
userId: this._convertId(processData.userId),
|
|
53
|
+
integrationId: this._convertId(processData.integrationId),
|
|
54
|
+
name: processData.name,
|
|
55
|
+
type: processData.type,
|
|
56
|
+
state: processData.state || 'INITIALIZING',
|
|
57
|
+
context: processData.context || {},
|
|
58
|
+
results: processData.results || {},
|
|
59
|
+
parentProcessId: this._convertId(processData.parentProcessId),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return this._toPlainObject(process);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find a process by ID
|
|
68
|
+
* @param {string} processId - Process ID to find
|
|
69
|
+
* @returns {Promise<Object|null>} Process record or null if not found
|
|
70
|
+
*/
|
|
71
|
+
async findById(processId) {
|
|
72
|
+
const process = await this.prisma.process.findUnique({
|
|
73
|
+
where: { id: this._convertId(processId) },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return process ? this._toPlainObject(process) : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Update a process record
|
|
81
|
+
* @param {string} processId - Process ID to update
|
|
82
|
+
* @param {Object} updates - Fields to update
|
|
83
|
+
* @returns {Promise<Object>} Updated process record
|
|
84
|
+
*/
|
|
85
|
+
async update(processId, updates) {
|
|
86
|
+
// Prepare update data, excluding undefined values
|
|
87
|
+
const updateData = {};
|
|
88
|
+
|
|
89
|
+
if (updates.state !== undefined) {
|
|
90
|
+
updateData.state = updates.state;
|
|
91
|
+
}
|
|
92
|
+
if (updates.context !== undefined) {
|
|
93
|
+
updateData.context = updates.context;
|
|
94
|
+
}
|
|
95
|
+
if (updates.results !== undefined) {
|
|
96
|
+
updateData.results = updates.results;
|
|
97
|
+
}
|
|
98
|
+
if (updates.parentProcessId !== undefined) {
|
|
99
|
+
updateData.parentProcessId = this._convertId(
|
|
100
|
+
updates.parentProcessId
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const process = await this.prisma.process.update({
|
|
105
|
+
where: { id: this._convertId(processId) },
|
|
106
|
+
data: updateData,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return this._toPlainObject(process);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Atomic process update — race-safe counterpart to `update()`.
|
|
114
|
+
*
|
|
115
|
+
* Compiles the `ProcessUpdateOps` into ONE `UPDATE "Process" ...
|
|
116
|
+
* RETURNING *` statement with nested `jsonb_set` calls for every
|
|
117
|
+
* context/results mutation. Postgres applies row-level locking
|
|
118
|
+
* during UPDATE, so concurrent callers on the same row serialize at
|
|
119
|
+
* the DB without any read-modify-write in Node.
|
|
120
|
+
*
|
|
121
|
+
* Path segments have been regex-validated upstream (see
|
|
122
|
+
* process-update-ops-shared.js); they are embedded directly into
|
|
123
|
+
* the SQL string. All values go through positional parameters.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} processId
|
|
126
|
+
* @param {ProcessUpdateOps} ops
|
|
127
|
+
* @returns {Promise<Object|null>}
|
|
128
|
+
*/
|
|
129
|
+
async applyProcessUpdate(processId, ops) {
|
|
130
|
+
const normalized = validateOps(ops);
|
|
131
|
+
const id = this._convertId(processId);
|
|
132
|
+
|
|
133
|
+
// Build the SQL expression for each JSON column. We start each
|
|
134
|
+
// column's expression from the column itself and wrap it in
|
|
135
|
+
// jsonb_set(...) calls — one wrap per operation targeting that
|
|
136
|
+
// column. If no op targets a column, we omit that SET clause so
|
|
137
|
+
// we don't issue a pointless self-assignment.
|
|
138
|
+
const params = [];
|
|
139
|
+
/** @type {(v:unknown)=>string} positional placeholder, 1-indexed */
|
|
140
|
+
const bind = (v) => {
|
|
141
|
+
params.push(v);
|
|
142
|
+
return `$${params.length}`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const columnExpressions = this._buildColumnExpressions(
|
|
146
|
+
normalized,
|
|
147
|
+
bind
|
|
148
|
+
);
|
|
149
|
+
const setClauses = [];
|
|
150
|
+
for (const [column, expr] of Object.entries(columnExpressions)) {
|
|
151
|
+
setClauses.push(`"${column}" = ${expr}`);
|
|
152
|
+
}
|
|
153
|
+
if (normalized.newState !== null) {
|
|
154
|
+
setClauses.push(`"state" = ${bind(normalized.newState)}`);
|
|
155
|
+
}
|
|
156
|
+
setClauses.push(`"updatedAt" = NOW()`);
|
|
157
|
+
|
|
158
|
+
const idPlaceholder = bind(id);
|
|
159
|
+
const sql = `
|
|
160
|
+
UPDATE "Process"
|
|
161
|
+
SET ${setClauses.join(', ')}
|
|
162
|
+
WHERE "id" = ${idPlaceholder}
|
|
163
|
+
RETURNING *
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const rows = await this.prisma.$queryRawUnsafe(sql, ...params);
|
|
167
|
+
if (!rows || rows.length === 0) return null;
|
|
168
|
+
return this._toPlainObject(rows[0]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns a map of column → SQL expression with all jsonb_set wraps
|
|
173
|
+
* applied. Used only by applyProcessUpdate.
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
_buildColumnExpressions(ops, bind) {
|
|
177
|
+
const byColumn = { context: null, results: null };
|
|
178
|
+
|
|
179
|
+
// Seed with the column itself (wrapped with COALESCE so that
|
|
180
|
+
// a NULL column doesn't break jsonb_set).
|
|
181
|
+
const seed = (col) =>
|
|
182
|
+
byColumn[col] ??
|
|
183
|
+
(byColumn[col] = `COALESCE("${col}", '{}'::jsonb)`);
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Postgres `jsonb_set(target, path, value, create_missing=true)`
|
|
187
|
+
* only creates the LEAF segment if missing — intermediate segments
|
|
188
|
+
* that don't exist as objects cause the call to return `target`
|
|
189
|
+
* unchanged (silent no-op). For a path like `context.a.b.c` on a
|
|
190
|
+
* doc where `context.a` is missing, we'd bail on the write.
|
|
191
|
+
*
|
|
192
|
+
* This helper wraps `prev` in a chain of `jsonb_set` calls that
|
|
193
|
+
* ensure each intermediate prefix path is an object, preserving
|
|
194
|
+
* its contents if it's already present:
|
|
195
|
+
*
|
|
196
|
+
* ensureParents(prev, ['a','b','c'])
|
|
197
|
+
* ⇒ jsonb_set(
|
|
198
|
+
* jsonb_set(prev, '{a}', COALESCE(prev#>'{a}', '{}'::jsonb), true),
|
|
199
|
+
* '{a,b}', COALESCE(${that}#>'{a,b}', '{}'::jsonb), true)
|
|
200
|
+
*
|
|
201
|
+
* The caller then wraps this result with its own `jsonb_set` for
|
|
202
|
+
* the leaf segment. Depth-1 paths skip this entirely (no parents
|
|
203
|
+
* to synthesize).
|
|
204
|
+
*/
|
|
205
|
+
const ensureParents = (prevExpr, segments) => {
|
|
206
|
+
let cur = prevExpr;
|
|
207
|
+
for (let i = 1; i < segments.length; i++) {
|
|
208
|
+
const parentPath = `'{${segments.slice(0, i).join(',')}}'`;
|
|
209
|
+
cur = `jsonb_set(${cur}, ${parentPath}, COALESCE(${cur} #> ${parentPath}, '{}'::jsonb), true)`;
|
|
210
|
+
}
|
|
211
|
+
return cur;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const wrapIncrement = (col, segments, delta) => {
|
|
215
|
+
const textPath = `'{${segments.join(',')}}'`;
|
|
216
|
+
const jsonbPath = `'{${segments.join(',')}}'`;
|
|
217
|
+
const prev = seed(col);
|
|
218
|
+
const guarded = ensureParents(prev, segments);
|
|
219
|
+
const nextValue = `to_jsonb(COALESCE((${guarded} #>> ${textPath})::numeric, 0) + ${bind(delta)})`;
|
|
220
|
+
byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${nextValue}, true)`;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const wrapSet = (col, segments, value) => {
|
|
224
|
+
const jsonbPath = `'{${segments.join(',')}}'`;
|
|
225
|
+
const prev = seed(col);
|
|
226
|
+
const guarded = ensureParents(prev, segments);
|
|
227
|
+
// $n::jsonb — values are serialized to JSON by Prisma when
|
|
228
|
+
// passed as a parameter, then cast back into jsonb.
|
|
229
|
+
byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${bind(JSON.stringify(value))}::jsonb, true)`;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const wrapPushSlice = (col, segments, spec) => {
|
|
233
|
+
const jsonbPath = `'{${segments.join(',')}}'`;
|
|
234
|
+
const prev = seed(col);
|
|
235
|
+
const guarded = ensureParents(prev, segments);
|
|
236
|
+
// Construct the sliced array in a CTE to evaluate `${newArr}`
|
|
237
|
+
// exactly ONCE (vs. the inline form that Postgres would still
|
|
238
|
+
// execute correctly but expand three times). Order is
|
|
239
|
+
// explicitly preserved by `jsonb_agg(... ORDER BY idx)`;
|
|
240
|
+
// without the ORDER BY, aggregate order is implementation-
|
|
241
|
+
// defined even with WITH ORDINALITY.
|
|
242
|
+
const sliced = `(
|
|
243
|
+
WITH combined AS (
|
|
244
|
+
SELECT COALESCE((${guarded} #> ${jsonbPath}), '[]'::jsonb) || ${bind(JSON.stringify(spec.values))}::jsonb AS arr
|
|
245
|
+
)
|
|
246
|
+
SELECT COALESCE(jsonb_agg(elem ORDER BY idx), '[]'::jsonb)
|
|
247
|
+
FROM combined,
|
|
248
|
+
jsonb_array_elements((SELECT arr FROM combined)) WITH ORDINALITY AS t(elem, idx)
|
|
249
|
+
WHERE idx > GREATEST(0, jsonb_array_length((SELECT arr FROM combined)) - ${bind(spec.keepLast)})
|
|
250
|
+
)`;
|
|
251
|
+
byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${sliced}, true)`;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
for (const [path, delta] of Object.entries(ops.increment)) {
|
|
255
|
+
const { column, segments } = splitPath(path);
|
|
256
|
+
wrapIncrement(column, segments, delta);
|
|
257
|
+
}
|
|
258
|
+
for (const [path, value] of Object.entries(ops.set)) {
|
|
259
|
+
const { column, segments } = splitPath(path);
|
|
260
|
+
wrapSet(column, segments, value);
|
|
261
|
+
}
|
|
262
|
+
for (const [path, spec] of Object.entries(ops.pushSlice)) {
|
|
263
|
+
const { column, segments } = splitPath(path);
|
|
264
|
+
wrapPushSlice(column, segments, spec);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result = {};
|
|
268
|
+
for (const [col, expr] of Object.entries(byColumn)) {
|
|
269
|
+
if (expr !== null) result[col] = expr;
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Find processes by integration and type
|
|
276
|
+
* @param {string} integrationId - Integration ID
|
|
277
|
+
* @param {string} type - Process type
|
|
278
|
+
* @returns {Promise<Array>} Array of process records
|
|
279
|
+
*/
|
|
280
|
+
async findByIntegrationAndType(integrationId, type) {
|
|
281
|
+
const processes = await this.prisma.process.findMany({
|
|
282
|
+
where: {
|
|
283
|
+
integrationId: this._convertId(integrationId),
|
|
284
|
+
type,
|
|
285
|
+
},
|
|
286
|
+
orderBy: {
|
|
287
|
+
createdAt: 'desc',
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return processes.map((p) => this._toPlainObject(p));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Find active processes (not in excluded states)
|
|
296
|
+
* @param {string} integrationId - Integration ID
|
|
297
|
+
* @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
|
|
298
|
+
* @returns {Promise<Array>} Array of active process records
|
|
299
|
+
*/
|
|
300
|
+
async findActiveProcesses(
|
|
301
|
+
integrationId,
|
|
302
|
+
excludeStates = ['COMPLETED', 'ERROR']
|
|
303
|
+
) {
|
|
304
|
+
const processes = await this.prisma.process.findMany({
|
|
305
|
+
where: {
|
|
306
|
+
integrationId: this._convertId(integrationId),
|
|
307
|
+
state: {
|
|
308
|
+
notIn: excludeStates,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
orderBy: {
|
|
312
|
+
createdAt: 'desc',
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return processes.map((p) => this._toPlainObject(p));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Find a process by name (most recent)
|
|
321
|
+
* @param {string} name - Process name
|
|
322
|
+
* @returns {Promise<Object|null>} Most recent process with given name, or null
|
|
323
|
+
*/
|
|
324
|
+
async findByName(name) {
|
|
325
|
+
const process = await this.prisma.process.findFirst({
|
|
326
|
+
where: { name },
|
|
327
|
+
orderBy: {
|
|
328
|
+
createdAt: 'desc',
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return process ? this._toPlainObject(process) : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Delete a process by ID
|
|
337
|
+
* @param {string} processId - Process ID to delete
|
|
338
|
+
* @returns {Promise<void>}
|
|
339
|
+
*/
|
|
340
|
+
async deleteById(processId) {
|
|
341
|
+
await this.prisma.process.delete({
|
|
342
|
+
where: { id: this._convertId(processId) },
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Convert Prisma model to plain JavaScript object
|
|
348
|
+
* Ensures consistent API across repository implementations
|
|
349
|
+
* @private
|
|
350
|
+
* @param {Object} process - Prisma process model
|
|
351
|
+
* @returns {Object} Plain process object
|
|
352
|
+
*/
|
|
353
|
+
_toPlainObject(process) {
|
|
354
|
+
return {
|
|
355
|
+
id: String(process.id),
|
|
356
|
+
userId: String(process.userId),
|
|
357
|
+
integrationId: String(process.integrationId),
|
|
358
|
+
name: process.name,
|
|
359
|
+
type: process.type,
|
|
360
|
+
state: process.state,
|
|
361
|
+
context: process.context,
|
|
362
|
+
results: process.results,
|
|
363
|
+
childProcesses: Array.isArray(process.childProcesses)
|
|
364
|
+
? process.childProcesses.length > 0 &&
|
|
365
|
+
typeof process.childProcesses[0] === 'object' &&
|
|
366
|
+
process.childProcesses[0] !== null
|
|
367
|
+
? process.childProcesses.map((child) => String(child.id))
|
|
368
|
+
: process.childProcesses
|
|
369
|
+
: [],
|
|
370
|
+
parentProcessId:
|
|
371
|
+
process.parentProcessId !== null
|
|
372
|
+
? String(process.parentProcessId)
|
|
373
|
+
: null,
|
|
374
|
+
createdAt: process.createdAt,
|
|
375
|
+
updatedAt: process.updatedAt,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
module.exports = { ProcessRepositoryPostgres };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for ProcessRepository.applyProcessUpdate() validation.
|
|
3
|
+
*
|
|
4
|
+
* These utilities are backend-agnostic: they enforce invariants on the
|
|
5
|
+
* `ProcessUpdateOps` shape BEFORE each adapter emits any SQL or database
|
|
6
|
+
* command. Keeping validation here means any bug we fix (e.g. tighter
|
|
7
|
+
* path regex, size cap) fixes all three adapters in one place.
|
|
8
|
+
*
|
|
9
|
+
* Imported by the Postgres, MongoDB, and DocumentDB adapters.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Allowed dot-path shape. Root must be `context` or `results`, and each
|
|
14
|
+
* segment after the first must be a JS-identifier-style token. Numeric
|
|
15
|
+
* segments (array indices) and bracket syntax are intentionally
|
|
16
|
+
* disallowed — array element mutation is exclusively handled via
|
|
17
|
+
* `pushSlice`, which targets a whole array at a path.
|
|
18
|
+
*/
|
|
19
|
+
const PATH_REGEX = /^(context|results)(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes and validates a `ProcessUpdateOps` object. Returns a frozen
|
|
23
|
+
* copy with defaults applied and every key pre-validated. Throws synchronously
|
|
24
|
+
* on any shape error so adapters can fail fast before touching the DB.
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} ops
|
|
27
|
+
* @returns {{
|
|
28
|
+
* increment: Record<string, number>,
|
|
29
|
+
* set: Record<string, unknown>,
|
|
30
|
+
* pushSlice: Record<string, { values: unknown[]; keepLast: number }>,
|
|
31
|
+
* newState: string|null,
|
|
32
|
+
* }}
|
|
33
|
+
*/
|
|
34
|
+
function validateOps(ops) {
|
|
35
|
+
if (!ops || typeof ops !== 'object' || Array.isArray(ops)) {
|
|
36
|
+
throw new Error('applyProcessUpdate: ops must be an object');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const increment = ops.increment || {};
|
|
40
|
+
const set = ops.set || {};
|
|
41
|
+
const pushSlice = ops.pushSlice || {};
|
|
42
|
+
const newState = ops.newState ?? null;
|
|
43
|
+
|
|
44
|
+
for (const [path, delta] of Object.entries(increment)) {
|
|
45
|
+
assertPath(path, 'increment');
|
|
46
|
+
if (typeof delta !== 'number' || !Number.isFinite(delta)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`applyProcessUpdate: increment['${path}'] must be a finite number, got ${typeof delta}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const path of Object.keys(set)) {
|
|
54
|
+
assertPath(path, 'set');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const [path, spec] of Object.entries(pushSlice)) {
|
|
58
|
+
assertPath(path, 'pushSlice');
|
|
59
|
+
if (
|
|
60
|
+
!spec ||
|
|
61
|
+
typeof spec !== 'object' ||
|
|
62
|
+
!Array.isArray(spec.values) ||
|
|
63
|
+
typeof spec.keepLast !== 'number' ||
|
|
64
|
+
!Number.isInteger(spec.keepLast) ||
|
|
65
|
+
spec.keepLast <= 0
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`applyProcessUpdate: pushSlice['${path}'] must be { values: [], keepLast: positive integer }`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (newState !== null && typeof newState !== 'string') {
|
|
74
|
+
throw new Error('applyProcessUpdate: newState must be a string');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hasAnyOp =
|
|
78
|
+
Object.keys(increment).length > 0 ||
|
|
79
|
+
Object.keys(set).length > 0 ||
|
|
80
|
+
Object.keys(pushSlice).length > 0 ||
|
|
81
|
+
newState !== null;
|
|
82
|
+
if (!hasAnyOp) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'applyProcessUpdate: at least one of increment/set/pushSlice/newState must be provided'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Object.freeze({ increment, set, pushSlice, newState });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function assertPath(path, opName) {
|
|
92
|
+
if (!PATH_REGEX.test(path)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`applyProcessUpdate: invalid path '${path}' in ${opName} (must match ${PATH_REGEX})`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Splits a validated path into `{ column, segments }`.
|
|
101
|
+
* `'context.pagination.pageCount'` → `{ column: 'context', segments: ['pagination', 'pageCount'] }`.
|
|
102
|
+
*/
|
|
103
|
+
function splitPath(path) {
|
|
104
|
+
const [column, ...segments] = path.split('.');
|
|
105
|
+
return { column, segments };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
PATH_REGEX,
|
|
110
|
+
validateOps,
|
|
111
|
+
splitPath,
|
|
112
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { IntegrationBase } = require('../../integration-base');
|
|
2
|
+
|
|
3
|
+
class ConfigCapturingModule {
|
|
4
|
+
static definition = {
|
|
5
|
+
getName: () => 'config-capturing-module'
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ConfigCapturingIntegration extends IntegrationBase {
|
|
10
|
+
static Definition = {
|
|
11
|
+
name: 'config-capturing',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
modules: {
|
|
14
|
+
primary: ConfigCapturingModule
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
label: 'Config Capturing Integration',
|
|
18
|
+
description: 'Test double for capturing config state during updates',
|
|
19
|
+
detailsUrl: 'https://example.com',
|
|
20
|
+
icon: 'test-icon'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static _capturedOnUpdateState = null;
|
|
25
|
+
|
|
26
|
+
static resetCaptures() {
|
|
27
|
+
this._capturedOnUpdateState = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static getCapturedOnUpdateState() {
|
|
31
|
+
return this._capturedOnUpdateState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
constructor(params) {
|
|
35
|
+
super(params);
|
|
36
|
+
this.integrationRepository = {
|
|
37
|
+
updateIntegrationById: jest.fn().mockResolvedValue({}),
|
|
38
|
+
findIntegrationById: jest.fn().mockResolvedValue({}),
|
|
39
|
+
};
|
|
40
|
+
this.updateIntegrationStatus = {
|
|
41
|
+
execute: jest.fn().mockResolvedValue({})
|
|
42
|
+
};
|
|
43
|
+
this.updateIntegrationMessages = {
|
|
44
|
+
execute: jest.fn().mockResolvedValue({})
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize() {
|
|
49
|
+
this.registerEventHandlers();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async onUpdate(params) {
|
|
53
|
+
ConfigCapturingIntegration._capturedOnUpdateState = {
|
|
54
|
+
thisConfig: JSON.parse(JSON.stringify(this.config)),
|
|
55
|
+
paramsConfig: params.config
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.config = this._deepMerge(this.config, params.config);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_deepMerge(target, source) {
|
|
62
|
+
const result = { ...target };
|
|
63
|
+
for (const key of Object.keys(source)) {
|
|
64
|
+
if (
|
|
65
|
+
source[key] !== null &&
|
|
66
|
+
typeof source[key] === 'object' &&
|
|
67
|
+
!Array.isArray(source[key]) &&
|
|
68
|
+
target[key] !== null &&
|
|
69
|
+
typeof target[key] === 'object' &&
|
|
70
|
+
!Array.isArray(target[key])
|
|
71
|
+
) {
|
|
72
|
+
result[key] = this._deepMerge(target[key], source[key]);
|
|
73
|
+
} else {
|
|
74
|
+
result[key] = source[key];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { ConfigCapturingIntegration };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { IntegrationBase } = require('../../integration-base');
|
|
2
|
+
|
|
3
|
+
class DummyModule {
|
|
4
|
+
static definition = {
|
|
5
|
+
getName: () => 'dummy'
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class DummyIntegration extends IntegrationBase {
|
|
10
|
+
static Definition = {
|
|
11
|
+
name: 'dummy',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
modules: {
|
|
14
|
+
dummy: DummyModule
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
label: 'Dummy Integration',
|
|
18
|
+
description: 'A dummy integration for testing',
|
|
19
|
+
detailsUrl: 'https://example.com',
|
|
20
|
+
icon: 'dummy-icon'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static getOptionDetails() {
|
|
25
|
+
return {
|
|
26
|
+
name: this.Definition.name,
|
|
27
|
+
version: this.Definition.version,
|
|
28
|
+
display: this.Definition.display
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(params) {
|
|
33
|
+
super(params);
|
|
34
|
+
this.sendSpy = jest.fn();
|
|
35
|
+
this.eventCallHistory = [];
|
|
36
|
+
this.events = {};
|
|
37
|
+
|
|
38
|
+
this.integrationRepository = {
|
|
39
|
+
updateIntegrationById: jest.fn().mockResolvedValue({}),
|
|
40
|
+
findIntegrationById: jest.fn().mockResolvedValue({}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.updateIntegrationStatus = {
|
|
44
|
+
execute: jest.fn().mockResolvedValue({})
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.updateIntegrationMessages = {
|
|
48
|
+
execute: jest.fn().mockResolvedValue({})
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async loadDynamicUserActions() {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async send(event, data) {
|
|
57
|
+
this.sendSpy(event, data);
|
|
58
|
+
this.eventCallHistory.push({ event, data, timestamp: Date.now() });
|
|
59
|
+
if (event === 'ON_UPDATE') {
|
|
60
|
+
await this.onUpdate(data);
|
|
61
|
+
}
|
|
62
|
+
return { event, data };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async initialize() {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async onCreate({ integrationId }) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async onUpdate(params) {
|
|
74
|
+
this.config = this._deepMerge(this.config, params.config);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_deepMerge(target, source) {
|
|
78
|
+
const result = { ...target };
|
|
79
|
+
for (const key of Object.keys(source)) {
|
|
80
|
+
if (
|
|
81
|
+
source[key] !== null &&
|
|
82
|
+
typeof source[key] === 'object' &&
|
|
83
|
+
!Array.isArray(source[key]) &&
|
|
84
|
+
target[key] !== null &&
|
|
85
|
+
typeof target[key] === 'object' &&
|
|
86
|
+
!Array.isArray(target[key])
|
|
87
|
+
) {
|
|
88
|
+
result[key] = this._deepMerge(target[key], source[key]);
|
|
89
|
+
} else {
|
|
90
|
+
result[key] = source[key];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async onDelete(params) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getConfig() {
|
|
101
|
+
return this.config || {};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { DummyIntegration };
|