@friggframework/core 2.0.0-next.6 → 2.0.0-next.61
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 +694 -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/user-commands.js +283 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +14 -7
- 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 +291 -0
- package/credential/repositories/credential-repository.js +302 -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 +61 -21
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +134 -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/health-check-repository.js +108 -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 +91 -0
- package/database/utils/mongodb-schema-init.js +106 -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/encrypt/test-encrypt.js +0 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- 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 +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22903 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -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/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 +360 -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 +341 -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 +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25077 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -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/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -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/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 +343 -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 +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +56 -0
- package/handlers/backend-utils.js +186 -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/integration-defined-workers.js +27 -0
- package/index.js +77 -22
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +326 -55
- package/integrations/integration-router.js +374 -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 +210 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +127 -0
- package/integrations/repositories/integration-repository-mongo.js +303 -0
- package/integrations/repositories/integration-repository-postgres.js +352 -0
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +90 -0
- package/integrations/repositories/process-repository-mongo.js +190 -0
- package/integrations/repositories/process-repository-postgres.js +217 -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 +99 -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 +201 -0
- package/integrations/use-cases/update-process-state.js +119 -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}/entity.js +1 -1
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module.js +221 -0
- package/modules/repositories/module-repository-documentdb.js +307 -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 +377 -0
- package/modules/repositories/module-repository-postgres.js +426 -0
- package/modules/repositories/module-repository.js +316 -0
- package/modules/requester/api-key.js +52 -0
- package/{module-plugin → modules}/requester/requester.js +1 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- 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 +31 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +133 -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 +360 -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/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +343 -0
- package/queues/queuer-util.js +27 -22
- 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/core/index.d.ts +2 -2
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -59
- package/types/syncs/index.d.ts +0 -2
- 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/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -45
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- 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/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/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
CredentialRepositoryInterface,
|
|
4
|
+
} = require('./credential-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prisma-based Credential Repository
|
|
8
|
+
* Handles OAuth credentials and API tokens persistence
|
|
9
|
+
*
|
|
10
|
+
* Works identically for both MongoDB and PostgreSQL:
|
|
11
|
+
* - MongoDB: String IDs with @db.ObjectId
|
|
12
|
+
* - PostgreSQL: Integer IDs with auto-increment
|
|
13
|
+
* - Both use same query patterns (no many-to-many differences)
|
|
14
|
+
*
|
|
15
|
+
* Migration from Mongoose:
|
|
16
|
+
* - Constructor injection of Prisma client
|
|
17
|
+
* - Dynamic schema (strict: false) → JSON field (data)
|
|
18
|
+
* - All OAuth tokens stored in data JSON field
|
|
19
|
+
* - Mongoose field names → Prisma field names (user → userId)
|
|
20
|
+
*/
|
|
21
|
+
class CredentialRepository extends CredentialRepositoryInterface {
|
|
22
|
+
constructor(prismaClient = prisma) {
|
|
23
|
+
super();
|
|
24
|
+
this.prisma = prismaClient; // Allow injection for testing
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find credential by ID
|
|
29
|
+
* Replaces: Credential.findById(id)
|
|
30
|
+
*
|
|
31
|
+
* @param {string} id - Credential ID
|
|
32
|
+
* @returns {Promise<Object|null>} Credential object or null
|
|
33
|
+
*/
|
|
34
|
+
async findCredentialById(id) {
|
|
35
|
+
const credential = await this.prisma.credential.findUnique({
|
|
36
|
+
where: { id },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!credential) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract data from JSON field
|
|
44
|
+
const data = credential.data || {};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
_id: credential.id,
|
|
48
|
+
id: credential.id,
|
|
49
|
+
user: credential.userId,
|
|
50
|
+
userId: credential.userId,
|
|
51
|
+
externalId: credential.externalId,
|
|
52
|
+
authIsValid: credential.authIsValid,
|
|
53
|
+
...data, // Spread OAuth tokens from JSON field
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Update authentication status
|
|
59
|
+
* Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
|
|
60
|
+
*
|
|
61
|
+
* @param {string} credentialId - Credential ID
|
|
62
|
+
* @param {boolean} authIsValid - Authentication validity status
|
|
63
|
+
* @returns {Promise<Object>} Update result
|
|
64
|
+
*/
|
|
65
|
+
async updateAuthenticationStatus(credentialId, authIsValid) {
|
|
66
|
+
await this.prisma.credential.update({
|
|
67
|
+
where: { id: credentialId },
|
|
68
|
+
data: { authIsValid },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return { acknowledged: true, modifiedCount: 1 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Permanently remove a credential document
|
|
76
|
+
* Replaces: Credential.deleteOne({ _id: credentialId })
|
|
77
|
+
*
|
|
78
|
+
* @param {string} credentialId - Credential ID
|
|
79
|
+
* @returns {Promise<Object>} Deletion result
|
|
80
|
+
*/
|
|
81
|
+
async deleteCredentialById(credentialId) {
|
|
82
|
+
try {
|
|
83
|
+
await this.prisma.credential.delete({
|
|
84
|
+
where: { id: credentialId },
|
|
85
|
+
});
|
|
86
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error.code === 'P2025') {
|
|
89
|
+
// Record not found
|
|
90
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create or update credential matching identifiers
|
|
98
|
+
* Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
|
|
99
|
+
*
|
|
100
|
+
* @param {{identifiers: Object, details: Object}} credentialDetails
|
|
101
|
+
* @returns {Promise<Object>} The persisted credential
|
|
102
|
+
*/
|
|
103
|
+
async upsertCredential(credentialDetails) {
|
|
104
|
+
const { identifiers, details } = credentialDetails;
|
|
105
|
+
if (!identifiers)
|
|
106
|
+
throw new Error('identifiers required to upsert credential');
|
|
107
|
+
|
|
108
|
+
// Build where clause from identifiers
|
|
109
|
+
const where = this._convertIdentifiersToWhere(identifiers);
|
|
110
|
+
|
|
111
|
+
// Separate schema fields from dynamic OAuth data
|
|
112
|
+
const {
|
|
113
|
+
user,
|
|
114
|
+
userId,
|
|
115
|
+
externalId,
|
|
116
|
+
authIsValid,
|
|
117
|
+
|
|
118
|
+
...oauthData
|
|
119
|
+
} = details;
|
|
120
|
+
|
|
121
|
+
// Find existing credential
|
|
122
|
+
const existing = await this.prisma.credential.findFirst({ where });
|
|
123
|
+
|
|
124
|
+
if (existing) {
|
|
125
|
+
// Update existing - merge OAuth data into existing data JSON
|
|
126
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
127
|
+
|
|
128
|
+
const updated = await this.prisma.credential.update({
|
|
129
|
+
where: { id: existing.id },
|
|
130
|
+
data: {
|
|
131
|
+
userId: userId || user || existing.userId,
|
|
132
|
+
externalId:
|
|
133
|
+
externalId !== undefined
|
|
134
|
+
? externalId
|
|
135
|
+
: existing.externalId,
|
|
136
|
+
authIsValid:
|
|
137
|
+
authIsValid !== undefined
|
|
138
|
+
? authIsValid
|
|
139
|
+
: existing.authIsValid,
|
|
140
|
+
data: mergedData,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
id: updated.id,
|
|
146
|
+
externalId: updated.externalId,
|
|
147
|
+
userId: updated.userId,
|
|
148
|
+
authIsValid: updated.authIsValid,
|
|
149
|
+
...(updated.data || {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create new credential
|
|
154
|
+
const created = await this.prisma.credential.create({
|
|
155
|
+
data: {
|
|
156
|
+
userId: userId || user,
|
|
157
|
+
externalId,
|
|
158
|
+
authIsValid: authIsValid,
|
|
159
|
+
|
|
160
|
+
data: oauthData,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
id: created.id,
|
|
166
|
+
externalId: created.externalId,
|
|
167
|
+
userId: created.userId,
|
|
168
|
+
authIsValid: created.authIsValid,
|
|
169
|
+
...(created.data || {}),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Find a credential by filter criteria
|
|
175
|
+
* Replaces: Credential.findOne(query)
|
|
176
|
+
*
|
|
177
|
+
* @param {Object} filter
|
|
178
|
+
* @param {string} [filter.userId] - User ID
|
|
179
|
+
* @param {string} [filter.externalId] - External ID
|
|
180
|
+
* @param {string} [filter.credentialId] - Credential ID
|
|
181
|
+
* @returns {Promise<Object|null>} Credential object or null if not found
|
|
182
|
+
*/
|
|
183
|
+
async findCredential(filter) {
|
|
184
|
+
const where = this._convertFilterToWhere(filter);
|
|
185
|
+
|
|
186
|
+
const credential = await this.prisma.credential.findFirst({
|
|
187
|
+
where,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!credential) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const data = credential.data || {};
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
id: credential.id,
|
|
198
|
+
userId: credential.userId,
|
|
199
|
+
externalId: credential.externalId,
|
|
200
|
+
authIsValid: credential.authIsValid,
|
|
201
|
+
access_token: data.access_token,
|
|
202
|
+
refresh_token: data.refresh_token,
|
|
203
|
+
domain: data.domain,
|
|
204
|
+
...data,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update a credential by ID
|
|
210
|
+
* Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
|
|
211
|
+
*
|
|
212
|
+
* @param {string} credentialId - Credential ID
|
|
213
|
+
* @param {Object} updates - Fields to update
|
|
214
|
+
* @returns {Promise<Object|null>} Updated credential object or null if not found
|
|
215
|
+
*/
|
|
216
|
+
async updateCredential(credentialId, updates) {
|
|
217
|
+
// Get existing credential to merge OAuth data
|
|
218
|
+
const existing = await this.prisma.credential.findUnique({
|
|
219
|
+
where: { id: credentialId },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!existing) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Separate schema fields from OAuth data
|
|
227
|
+
const {
|
|
228
|
+
user,
|
|
229
|
+
userId,
|
|
230
|
+
externalId,
|
|
231
|
+
authIsValid,
|
|
232
|
+
|
|
233
|
+
...oauthData
|
|
234
|
+
} = updates;
|
|
235
|
+
|
|
236
|
+
// Merge OAuth data with existing
|
|
237
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
238
|
+
|
|
239
|
+
const updated = await this.prisma.credential.update({
|
|
240
|
+
where: { id: credentialId },
|
|
241
|
+
data: {
|
|
242
|
+
userId: userId || user || existing.userId,
|
|
243
|
+
externalId:
|
|
244
|
+
externalId !== undefined ? externalId : existing.externalId,
|
|
245
|
+
authIsValid:
|
|
246
|
+
authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
247
|
+
data: mergedData,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const data = updated.data || {};
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
id: updated.id,
|
|
255
|
+
userId: updated.userId,
|
|
256
|
+
externalId: updated.externalId,
|
|
257
|
+
authIsValid: updated.authIsValid,
|
|
258
|
+
access_token: data.access_token,
|
|
259
|
+
refresh_token: data.refresh_token,
|
|
260
|
+
domain: data.domain,
|
|
261
|
+
...data,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Convert identifiers to Prisma where clause
|
|
267
|
+
* @private
|
|
268
|
+
* @param {Object} identifiers - Identifier fields
|
|
269
|
+
* @returns {Object} Prisma where clause
|
|
270
|
+
*/
|
|
271
|
+
_convertIdentifiersToWhere(identifiers) {
|
|
272
|
+
const where = {};
|
|
273
|
+
|
|
274
|
+
if (identifiers._id) where.id = identifiers._id;
|
|
275
|
+
if (identifiers.id) where.id = identifiers.id;
|
|
276
|
+
if (identifiers.user) where.userId = identifiers.user;
|
|
277
|
+
if (identifiers.userId) where.userId = identifiers.userId;
|
|
278
|
+
if (identifiers.externalId) where.externalId = identifiers.externalId;
|
|
279
|
+
|
|
280
|
+
return where;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Convert filter to Prisma where clause
|
|
285
|
+
* @private
|
|
286
|
+
* @param {Object} filter - Filter criteria
|
|
287
|
+
* @returns {Object} Prisma where clause
|
|
288
|
+
*/
|
|
289
|
+
_convertFilterToWhere(filter) {
|
|
290
|
+
const where = {};
|
|
291
|
+
|
|
292
|
+
if (filter.credentialId) where.id = filter.credentialId;
|
|
293
|
+
if (filter.id) where.id = filter.id;
|
|
294
|
+
if (filter.user) where.userId = filter.user;
|
|
295
|
+
if (filter.userId) where.userId = filter.userId;
|
|
296
|
+
if (filter.externalId) where.externalId = filter.externalId;
|
|
297
|
+
|
|
298
|
+
return where;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { CredentialRepository };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class GetCredentialForUser {
|
|
2
|
+
constructor({ credentialRepository }) {
|
|
3
|
+
this.credentialRepository = credentialRepository;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async execute(credentialId, userId) {
|
|
7
|
+
const credential = await this.credentialRepository.findCredentialById(
|
|
8
|
+
credentialId
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (!credential) {
|
|
12
|
+
throw new Error(`Credential with id ${credentialId} not found`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (credential.userId.toString() !== userId.toString()) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Credential ${credentialId} does not belong to user ${userId}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return credential;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { GetCredentialForUser };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class UpdateAuthenticationStatus {
|
|
2
|
+
constructor({ credentialRepository }) {
|
|
3
|
+
this.credentialRepository = credentialRepository;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} credentialId
|
|
8
|
+
* @param {boolean} authIsValid
|
|
9
|
+
*/
|
|
10
|
+
async execute(credentialId, authIsValid) {
|
|
11
|
+
await this.credentialRepository.updateAuthenticationStatus(credentialId, authIsValid);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { UpdateAuthenticationStatus };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# MongoDB Transaction Namespace Fix
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
The encryption health check was failing with the following error:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Cannot create namespace frigg.Credential in multi-document transaction.
|
|
9
|
+
Error code: 263
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Root Cause
|
|
13
|
+
|
|
14
|
+
MongoDB does not allow creating collections (namespaces) inside multi-document transactions. When Prisma tries to create a document in a collection that doesn't exist yet, MongoDB needs to implicitly create the collection. If this happens inside a transaction context, MongoDB throws error code 263.
|
|
15
|
+
|
|
16
|
+
### Technical Details
|
|
17
|
+
|
|
18
|
+
- **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
|
|
19
|
+
- **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
|
|
20
|
+
- **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
|
|
21
|
+
|
|
22
|
+
## Solution
|
|
23
|
+
|
|
24
|
+
**Implemented a comprehensive schema initialization system that ensures all collections exist at application startup.**
|
|
25
|
+
|
|
26
|
+
### Architectural Approach
|
|
27
|
+
|
|
28
|
+
Rather than checking before each individual database operation, we take a **systematic, fail-fast approach**:
|
|
29
|
+
|
|
30
|
+
1. **Parse Prisma Schema**: Extract all collection names from the Prisma schema definition
|
|
31
|
+
2. **Initialize at Startup**: Create all collections when the database connection is established
|
|
32
|
+
3. **Fail Fast**: If there are database issues, the application fails immediately at startup rather than during runtime operations
|
|
33
|
+
4. **Idempotent**: Safe to run multiple times - only creates collections that don't exist
|
|
34
|
+
|
|
35
|
+
This follows the **"fail fast"** principle and ensures consistent state across all application instances.
|
|
36
|
+
|
|
37
|
+
### Changes Made
|
|
38
|
+
|
|
39
|
+
1. **Created MongoDB Schema Initialization** (`packages/core/database/utils/mongodb-schema-init.js`)
|
|
40
|
+
- `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
|
|
41
|
+
- `getPrismaCollections()` - Returns list of all Prisma collection names
|
|
42
|
+
- `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
|
|
43
|
+
- Only runs for MongoDB (skips PostgreSQL)
|
|
44
|
+
- Fails fast if database not connected
|
|
45
|
+
|
|
46
|
+
2. **Created MongoDB Collection Utilities** (`packages/core/database/utils/mongodb-collection-utils.js`)
|
|
47
|
+
- `ensureCollectionExists(collectionName)` - Ensures a single collection exists
|
|
48
|
+
- `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
|
|
49
|
+
- `collectionExists(collectionName)` - Checks if a collection exists
|
|
50
|
+
- Handles race conditions gracefully (NamespaceExists errors)
|
|
51
|
+
|
|
52
|
+
3. **Integrated into Database Connection** (`packages/core/database/prisma.js`)
|
|
53
|
+
- Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
|
|
54
|
+
- Ensures all collections exist before application handles requests
|
|
55
|
+
|
|
56
|
+
4. **Updated Health Check Repository** (`packages/core/database/repositories/health-check-repository-mongodb.js`)
|
|
57
|
+
- Removed per-operation collection existence checks
|
|
58
|
+
- Added documentation noting schema is initialized at startup
|
|
59
|
+
|
|
60
|
+
5. **Added Comprehensive Tests**
|
|
61
|
+
- `mongodb-schema-init.test.js` - Tests schema initialization system
|
|
62
|
+
- `mongodb-collection-utils.test.js` - Tests collection utility functions
|
|
63
|
+
- Tests error handling, race conditions, and edge cases
|
|
64
|
+
|
|
65
|
+
### Implementation Flow
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// 1. Application startup - connect to database
|
|
69
|
+
await connectPrisma();
|
|
70
|
+
└─> await initializeMongoDBSchema();
|
|
71
|
+
└─> await ensureCollectionsExist([
|
|
72
|
+
'User', 'Token', 'Credential', 'Entity',
|
|
73
|
+
'Integration', 'IntegrationMapping', 'Process',
|
|
74
|
+
'Sync', 'DataIdentifier', 'Association',
|
|
75
|
+
'AssociationObject', 'State', 'WebsocketConnection'
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
// 2. Now all collections exist - safe to handle requests
|
|
79
|
+
// No per-operation checks needed!
|
|
80
|
+
await prisma.credential.create({ data: {...} }); // Works without namespace error
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Best Practices Followed
|
|
84
|
+
|
|
85
|
+
1. **Domain-Driven Design**: Created reusable utility module for MongoDB-specific concerns
|
|
86
|
+
2. **Hexagonal Architecture**: Infrastructure concerns (schema initialization) handled in infrastructure layer
|
|
87
|
+
3. **Test-Driven Development**: Added comprehensive tests for all utility functions
|
|
88
|
+
4. **Fail Fast Principle**: Database issues discovered at startup, not during runtime
|
|
89
|
+
5. **Idempotency**: Safe to run multiple times across multiple instances
|
|
90
|
+
6. **Error Handling**: Graceful degradation on race conditions and errors
|
|
91
|
+
7. **Documentation**: Inline comments, JSDoc, and comprehensive documentation
|
|
92
|
+
|
|
93
|
+
## Benefits
|
|
94
|
+
|
|
95
|
+
### Immediate Benefits
|
|
96
|
+
- ✅ Fixes encryption health check failures on fresh databases
|
|
97
|
+
- ✅ Prevents transaction namespace errors across **all** Prisma operations
|
|
98
|
+
- ✅ No per-operation overhead - collections created once at startup
|
|
99
|
+
- ✅ Fail fast - database issues discovered immediately at startup
|
|
100
|
+
- ✅ Idempotent - safe to run multiple times and across multiple instances
|
|
101
|
+
|
|
102
|
+
### Architectural Benefits
|
|
103
|
+
- ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
|
|
104
|
+
- ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
|
|
105
|
+
- ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
|
|
106
|
+
- ✅ **No repository-level checks needed**: All repositories benefit automatically
|
|
107
|
+
- ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
|
|
108
|
+
|
|
109
|
+
### Operational Benefits
|
|
110
|
+
- ✅ **Predictable startup**: Clear logging of schema initialization
|
|
111
|
+
- ✅ **Zero runtime overhead**: Collections created once, not on every operation
|
|
112
|
+
- ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
|
|
113
|
+
|
|
114
|
+
## Design Decisions
|
|
115
|
+
|
|
116
|
+
### Why Initialize at Startup?
|
|
117
|
+
|
|
118
|
+
We considered two approaches:
|
|
119
|
+
|
|
120
|
+
**❌ Per-Operation Checks (Initial approach)**
|
|
121
|
+
```javascript
|
|
122
|
+
async createCredential(data) {
|
|
123
|
+
await ensureCollectionExists('Credential'); // Check every time
|
|
124
|
+
return await prisma.credential.create({ data });
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
- Pros: Guarantees collection exists before each operation
|
|
128
|
+
- Cons: Runtime overhead, repeated checks, scattered logic
|
|
129
|
+
|
|
130
|
+
**✅ Startup Initialization (Final approach)**
|
|
131
|
+
```javascript
|
|
132
|
+
// Once at startup
|
|
133
|
+
await connectPrisma(); // Initializes all collections
|
|
134
|
+
|
|
135
|
+
// All operations just work
|
|
136
|
+
async createCredential(data) {
|
|
137
|
+
return await prisma.credential.create({ data }); // No checks needed
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
- Pros: Zero runtime overhead, centralized logic, fail fast, consistent
|
|
141
|
+
- Cons: Requires database connection at startup (already required)
|
|
142
|
+
|
|
143
|
+
### Benefits of Startup Approach
|
|
144
|
+
|
|
145
|
+
1. **Performance**: Collections created once vs. checking before every operation
|
|
146
|
+
2. **Simplicity**: No conditional logic in repositories
|
|
147
|
+
3. **Reliability**: Fail fast at startup if database has issues
|
|
148
|
+
4. **Maintainability**: Single source of truth for schema initialization
|
|
149
|
+
5. **DDD Alignment**: Infrastructure concerns handled in infrastructure layer
|
|
150
|
+
|
|
151
|
+
## Logging Output
|
|
152
|
+
|
|
153
|
+
When the application starts, you'll see clear logging:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Initializing MongoDB schema - ensuring all collections exist...
|
|
157
|
+
Created MongoDB collection: Credential
|
|
158
|
+
MongoDB schema initialization complete - 13 collections verified (45ms)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
On subsequent startups (collections already exist):
|
|
162
|
+
```
|
|
163
|
+
Initializing MongoDB schema - ensuring all collections exist...
|
|
164
|
+
MongoDB schema initialization complete - 13 collections verified (12ms)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## References
|
|
168
|
+
|
|
169
|
+
- [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
|
|
170
|
+
- [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
|
|
171
|
+
- [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
|
|
172
|
+
- [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
|
|
173
|
+
|
|
174
|
+
## Future Considerations
|
|
175
|
+
|
|
176
|
+
### Automatic Schema Sync
|
|
177
|
+
Consider enhancing the system to:
|
|
178
|
+
- Parse Prisma schema file dynamically to extract collection names
|
|
179
|
+
- Auto-detect schema changes and create new collections
|
|
180
|
+
- Provide CLI command for manual schema initialization
|
|
181
|
+
|
|
182
|
+
### Migration Support
|
|
183
|
+
For production deployments with existing data:
|
|
184
|
+
- Document migration procedures for new collections
|
|
185
|
+
- Consider pre-migration scripts for blue-green deployments
|
|
186
|
+
- Add health check for schema initialization status
|
|
187
|
+
|
|
188
|
+
### Multi-Database Support
|
|
189
|
+
The system already handles:
|
|
190
|
+
- ✅ MongoDB - Full schema initialization
|
|
191
|
+
- ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
|
|
192
|
+
- Consider adding explicit migration support for DocumentDB-specific features
|
|
193
|
+
|
|
194
|
+
### Index Creation
|
|
195
|
+
Future enhancement could also create indexes at startup:
|
|
196
|
+
- Parse Prisma schema for `@@index` directives
|
|
197
|
+
- Create indexes if they don't exist
|
|
198
|
+
- Provide index health checks
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lambda Invoker Adapter
|
|
3
|
+
* Infrastructure layer - handles AWS Lambda function invocations
|
|
4
|
+
*
|
|
5
|
+
* Part of Hexagonal Architecture:
|
|
6
|
+
* - Infrastructure Layer adapter for AWS SDK
|
|
7
|
+
* - Used by Domain Layer use cases
|
|
8
|
+
* - Isolates AWS-specific logic from business logic
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom error for Lambda invocation failures
|
|
15
|
+
* Provides structured error information for debugging
|
|
16
|
+
*/
|
|
17
|
+
class LambdaInvocationError extends Error {
|
|
18
|
+
constructor(message, functionName, statusCode) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'LambdaInvocationError';
|
|
21
|
+
this.functionName = functionName;
|
|
22
|
+
this.statusCode = statusCode;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Adapter for invoking AWS Lambda functions
|
|
28
|
+
*
|
|
29
|
+
* Infrastructure layer - handles AWS SDK communication
|
|
30
|
+
* Converts AWS SDK responses to domain-friendly formats
|
|
31
|
+
*/
|
|
32
|
+
class LambdaInvoker {
|
|
33
|
+
/**
|
|
34
|
+
* @param {LambdaClient} lambdaClient - AWS Lambda client (injected for testability)
|
|
35
|
+
*/
|
|
36
|
+
constructor(lambdaClient = new LambdaClient({})) {
|
|
37
|
+
this.client = lambdaClient;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Invoke Lambda function synchronously
|
|
42
|
+
*
|
|
43
|
+
* @param {string} functionName - Lambda function name or ARN
|
|
44
|
+
* @param {Object} payload - Event payload to send to Lambda
|
|
45
|
+
* @returns {Promise<Object>} Parsed response body
|
|
46
|
+
* @throws {LambdaInvocationError} If Lambda returns error status
|
|
47
|
+
* @throws {Error} If AWS SDK call fails
|
|
48
|
+
*/
|
|
49
|
+
async invoke(functionName, payload) {
|
|
50
|
+
try {
|
|
51
|
+
const command = new InvokeCommand({
|
|
52
|
+
FunctionName: functionName,
|
|
53
|
+
InvocationType: 'RequestResponse', // Synchronous
|
|
54
|
+
Payload: JSON.stringify(payload),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const response = await this.client.send(command);
|
|
58
|
+
|
|
59
|
+
// Parse response payload
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = JSON.parse(Buffer.from(response.Payload).toString());
|
|
63
|
+
} catch (parseError) {
|
|
64
|
+
throw new LambdaInvocationError(
|
|
65
|
+
`Failed to parse Lambda response: ${parseError.message}`,
|
|
66
|
+
functionName,
|
|
67
|
+
null
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check status code
|
|
72
|
+
if (result.statusCode === 200) {
|
|
73
|
+
return result.body;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Lambda returned error status
|
|
77
|
+
const errorMessage = result.body?.error || 'Lambda invocation failed';
|
|
78
|
+
throw new LambdaInvocationError(
|
|
79
|
+
`Lambda ${functionName} returned error: ${errorMessage}`,
|
|
80
|
+
functionName,
|
|
81
|
+
result.statusCode
|
|
82
|
+
);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Re-throw LambdaInvocationError as-is
|
|
85
|
+
if (error instanceof LambdaInvocationError) {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Wrap AWS SDK errors
|
|
90
|
+
throw new Error(`Failed to invoke Lambda ${functionName}: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { LambdaInvoker, LambdaInvocationError };
|
|
96
|
+
|
|
97
|
+
|