@friggframework/core 2.0.0-next.5 → 2.0.0-next.50

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.
Files changed (267) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +179 -0
  7. package/application/commands/user-commands.js +213 -0
  8. package/application/index.js +69 -0
  9. package/core/CLAUDE.md +690 -0
  10. package/core/Worker.js +8 -21
  11. package/core/create-handler.js +2 -7
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +307 -0
  15. package/credential/repositories/credential-repository-postgres.js +313 -0
  16. package/credential/repositories/credential-repository.js +302 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  20. package/database/adapters/lambda-invoker.js +97 -0
  21. package/database/config.js +154 -0
  22. package/database/encryption/README.md +684 -0
  23. package/database/encryption/encryption-schema-registry.js +141 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/logger.js +79 -0
  26. package/database/encryption/prisma-encryption-extension.js +222 -0
  27. package/database/index.js +25 -12
  28. package/database/models/WebsocketConnection.js +16 -10
  29. package/database/models/readme.md +1 -0
  30. package/database/prisma.js +222 -0
  31. package/database/repositories/health-check-repository-factory.js +43 -0
  32. package/database/repositories/health-check-repository-interface.js +87 -0
  33. package/database/repositories/health-check-repository-mongodb.js +91 -0
  34. package/database/repositories/health-check-repository-postgres.js +82 -0
  35. package/database/repositories/health-check-repository.js +108 -0
  36. package/database/repositories/migration-status-repository-s3.js +137 -0
  37. package/database/use-cases/check-database-health-use-case.js +29 -0
  38. package/database/use-cases/check-database-state-use-case.js +81 -0
  39. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  40. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  41. package/database/use-cases/get-migration-status-use-case.js +93 -0
  42. package/database/use-cases/run-database-migration-use-case.js +137 -0
  43. package/database/use-cases/test-encryption-use-case.js +253 -0
  44. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  45. package/database/utils/mongodb-collection-utils.js +91 -0
  46. package/database/utils/mongodb-schema-init.js +106 -0
  47. package/database/utils/prisma-runner.js +400 -0
  48. package/database/utils/prisma-schema-parser.js +182 -0
  49. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  50. package/encrypt/Cryptor.js +34 -168
  51. package/encrypt/index.js +1 -2
  52. package/encrypt/test-encrypt.js +0 -2
  53. package/generated/prisma-mongodb/client.d.ts +1 -0
  54. package/generated/prisma-mongodb/client.js +4 -0
  55. package/generated/prisma-mongodb/default.d.ts +1 -0
  56. package/generated/prisma-mongodb/default.js +4 -0
  57. package/generated/prisma-mongodb/edge.d.ts +1 -0
  58. package/generated/prisma-mongodb/edge.js +334 -0
  59. package/generated/prisma-mongodb/index-browser.js +316 -0
  60. package/generated/prisma-mongodb/index.d.ts +22898 -0
  61. package/generated/prisma-mongodb/index.js +359 -0
  62. package/generated/prisma-mongodb/package.json +183 -0
  63. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  64. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  65. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  66. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  67. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  68. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  69. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  70. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  71. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  72. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  73. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  74. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  75. package/generated/prisma-mongodb/schema.prisma +362 -0
  76. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  77. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  78. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  79. package/generated/prisma-mongodb/wasm.js +341 -0
  80. package/generated/prisma-postgresql/client.d.ts +1 -0
  81. package/generated/prisma-postgresql/client.js +4 -0
  82. package/generated/prisma-postgresql/default.d.ts +1 -0
  83. package/generated/prisma-postgresql/default.js +4 -0
  84. package/generated/prisma-postgresql/edge.d.ts +1 -0
  85. package/generated/prisma-postgresql/edge.js +356 -0
  86. package/generated/prisma-postgresql/index-browser.js +338 -0
  87. package/generated/prisma-postgresql/index.d.ts +25072 -0
  88. package/generated/prisma-postgresql/index.js +381 -0
  89. package/generated/prisma-postgresql/package.json +183 -0
  90. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  91. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  92. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  93. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  94. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  95. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  96. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  97. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  98. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  99. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  100. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  101. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  102. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  103. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  104. package/generated/prisma-postgresql/schema.prisma +345 -0
  105. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  106. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  107. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  108. package/generated/prisma-postgresql/wasm.js +363 -0
  109. package/handlers/WEBHOOKS.md +653 -0
  110. package/handlers/app-definition-loader.js +38 -0
  111. package/handlers/app-handler-helpers.js +56 -0
  112. package/handlers/backend-utils.js +180 -0
  113. package/handlers/database-migration-handler.js +227 -0
  114. package/handlers/integration-event-dispatcher.js +54 -0
  115. package/handlers/routers/HEALTHCHECK.md +342 -0
  116. package/handlers/routers/auth.js +15 -0
  117. package/handlers/routers/db-migration.handler.js +29 -0
  118. package/handlers/routers/db-migration.js +256 -0
  119. package/handlers/routers/health.js +519 -0
  120. package/handlers/routers/integration-defined-routers.js +45 -0
  121. package/handlers/routers/integration-webhook-routers.js +67 -0
  122. package/handlers/routers/user.js +63 -0
  123. package/handlers/routers/websocket.js +57 -0
  124. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  125. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  126. package/handlers/workers/db-migration.js +352 -0
  127. package/handlers/workers/integration-defined-workers.js +27 -0
  128. package/index.js +77 -22
  129. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  130. package/integrations/index.js +12 -10
  131. package/integrations/integration-base.js +296 -54
  132. package/integrations/integration-router.js +381 -182
  133. package/integrations/options.js +1 -1
  134. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  135. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  136. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  137. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  138. package/integrations/repositories/integration-mapping-repository.js +156 -0
  139. package/integrations/repositories/integration-repository-factory.js +44 -0
  140. package/integrations/repositories/integration-repository-interface.js +127 -0
  141. package/integrations/repositories/integration-repository-mongo.js +303 -0
  142. package/integrations/repositories/integration-repository-postgres.js +352 -0
  143. package/integrations/repositories/process-repository-factory.js +46 -0
  144. package/integrations/repositories/process-repository-interface.js +90 -0
  145. package/integrations/repositories/process-repository-mongo.js +190 -0
  146. package/integrations/repositories/process-repository-postgres.js +217 -0
  147. package/integrations/tests/doubles/dummy-integration-class.js +83 -0
  148. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  149. package/integrations/use-cases/create-integration.js +83 -0
  150. package/integrations/use-cases/create-process.js +128 -0
  151. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  152. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  153. package/integrations/use-cases/get-integration-for-user.js +78 -0
  154. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  155. package/integrations/use-cases/get-integration-instance.js +83 -0
  156. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  157. package/integrations/use-cases/get-possible-integrations.js +27 -0
  158. package/integrations/use-cases/get-process.js +87 -0
  159. package/integrations/use-cases/index.js +19 -0
  160. package/integrations/use-cases/load-integration-context.js +71 -0
  161. package/integrations/use-cases/update-integration-messages.js +44 -0
  162. package/integrations/use-cases/update-integration-status.js +32 -0
  163. package/integrations/use-cases/update-integration.js +93 -0
  164. package/integrations/use-cases/update-process-metrics.js +201 -0
  165. package/integrations/use-cases/update-process-state.js +119 -0
  166. package/integrations/utils/map-integration-dto.js +36 -0
  167. package/jest-global-setup-noop.js +3 -0
  168. package/jest-global-teardown-noop.js +3 -0
  169. package/logs/logger.js +0 -4
  170. package/{module-plugin → modules}/entity.js +1 -1
  171. package/{module-plugin → modules}/index.js +0 -8
  172. package/modules/module-factory.js +56 -0
  173. package/modules/module.js +221 -0
  174. package/modules/repositories/module-repository-factory.js +33 -0
  175. package/modules/repositories/module-repository-interface.js +129 -0
  176. package/modules/repositories/module-repository-mongo.js +377 -0
  177. package/modules/repositories/module-repository-postgres.js +426 -0
  178. package/modules/repositories/module-repository.js +316 -0
  179. package/{module-plugin → modules}/requester/requester.js +1 -0
  180. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  181. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  182. package/modules/tests/doubles/test-module-factory.js +16 -0
  183. package/modules/tests/doubles/test-module-repository.js +39 -0
  184. package/modules/use-cases/get-entities-for-user.js +32 -0
  185. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  186. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  187. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  188. package/modules/use-cases/get-module.js +55 -0
  189. package/modules/use-cases/process-authorization-callback.js +122 -0
  190. package/modules/use-cases/refresh-entity-options.js +59 -0
  191. package/modules/use-cases/test-module-auth.js +55 -0
  192. package/modules/utils/map-module-dto.js +18 -0
  193. package/package.json +82 -50
  194. package/prisma-mongodb/schema.prisma +362 -0
  195. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  196. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  197. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  198. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  199. package/prisma-postgresql/schema.prisma +345 -0
  200. package/queues/queuer-util.js +28 -15
  201. package/syncs/manager.js +468 -443
  202. package/syncs/repositories/sync-repository-factory.js +38 -0
  203. package/syncs/repositories/sync-repository-interface.js +109 -0
  204. package/syncs/repositories/sync-repository-mongo.js +239 -0
  205. package/syncs/repositories/sync-repository-postgres.js +319 -0
  206. package/syncs/sync.js +0 -1
  207. package/token/repositories/token-repository-factory.js +33 -0
  208. package/token/repositories/token-repository-interface.js +131 -0
  209. package/token/repositories/token-repository-mongo.js +212 -0
  210. package/token/repositories/token-repository-postgres.js +257 -0
  211. package/token/repositories/token-repository.js +219 -0
  212. package/types/core/index.d.ts +2 -2
  213. package/types/integrations/index.d.ts +2 -6
  214. package/types/module-plugin/index.d.ts +5 -59
  215. package/types/syncs/index.d.ts +0 -2
  216. package/user/repositories/user-repository-factory.js +46 -0
  217. package/user/repositories/user-repository-interface.js +198 -0
  218. package/user/repositories/user-repository-mongo.js +291 -0
  219. package/user/repositories/user-repository-postgres.js +350 -0
  220. package/user/tests/doubles/test-user-repository.js +72 -0
  221. package/user/use-cases/authenticate-user.js +127 -0
  222. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  223. package/user/use-cases/create-individual-user.js +61 -0
  224. package/user/use-cases/create-organization-user.js +47 -0
  225. package/user/use-cases/create-token-for-user-id.js +30 -0
  226. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  227. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  228. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  229. package/user/use-cases/login-user.js +122 -0
  230. package/user/user.js +93 -0
  231. package/utils/backend-path.js +38 -0
  232. package/utils/index.js +6 -0
  233. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  234. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  235. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  236. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  237. package/websocket/repositories/websocket-connection-repository.js +161 -0
  238. package/database/models/State.js +0 -9
  239. package/database/models/Token.js +0 -70
  240. package/database/mongo.js +0 -45
  241. package/encrypt/Cryptor.test.js +0 -32
  242. package/encrypt/encrypt.js +0 -132
  243. package/encrypt/encrypt.test.js +0 -1069
  244. package/errors/base-error.test.js +0 -32
  245. package/errors/fetch-error.test.js +0 -79
  246. package/errors/halt-error.test.js +0 -11
  247. package/errors/validation-errors.test.js +0 -120
  248. package/integrations/create-frigg-backend.js +0 -31
  249. package/integrations/integration-factory.js +0 -251
  250. package/integrations/integration-mapping.js +0 -43
  251. package/integrations/integration-model.js +0 -46
  252. package/integrations/integration-user.js +0 -144
  253. package/integrations/test/integration-base.test.js +0 -144
  254. package/lambda/TimeoutCatcher.test.js +0 -68
  255. package/logs/logger.test.js +0 -76
  256. package/module-plugin/auther.js +0 -393
  257. package/module-plugin/credential.js +0 -22
  258. package/module-plugin/entity-manager.js +0 -70
  259. package/module-plugin/manager.js +0 -169
  260. package/module-plugin/module-factory.js +0 -61
  261. package/module-plugin/requester/requester.test.js +0 -28
  262. package/module-plugin/test/auther.test.js +0 -97
  263. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  264. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  265. /package/{module-plugin → modules}/requester/basic.js +0 -0
  266. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  267. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * MongoDB Schema Initialization for Prisma
3
+ *
4
+ * Dynamically parses the Prisma schema and ensures all collections exist before
5
+ * the application starts handling requests. This prevents
6
+ * "Cannot create namespace in multi-document transaction" errors.
7
+ *
8
+ * MongoDB does not allow creating collections inside transactions.
9
+ * By pre-creating all collections at startup, we ensure all Prisma
10
+ * operations can safely use transactions without namespace creation errors.
11
+ *
12
+ * Collection names are extracted dynamically from the Prisma schema file,
13
+ * ensuring they stay in sync with schema changes without manual updates.
14
+ *
15
+ * @see https://github.com/prisma/prisma/issues/8305
16
+ * @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
17
+ */
18
+
19
+ const { mongoose } = require('../mongoose');
20
+ const { ensureCollectionsExist } = require('./mongodb-collection-utils');
21
+ const { getCollectionsFromSchemaSync } = require('./prisma-schema-parser');
22
+ const config = require('../config');
23
+
24
+ /**
25
+ * Initialize MongoDB schema by ensuring all collections exist
26
+ *
27
+ * This should be called once at application startup, after the database
28
+ * connection is established but before handling any requests.
29
+ *
30
+ * Dynamically parses the Prisma schema to extract collection names,
31
+ * ensuring automatic sync with schema changes.
32
+ *
33
+ * Benefits:
34
+ * - Prevents transaction namespace creation errors
35
+ * - Fails fast if there are database connection issues
36
+ * - Ensures consistent state across all instances
37
+ * - Idempotent - safe to run multiple times
38
+ * - Automatically syncs with Prisma schema changes
39
+ *
40
+ * @returns {Promise<void>}
41
+ *
42
+ * @example
43
+ * ```js
44
+ * await connectPrisma();
45
+ * await initializeMongoDBSchema(); // Run after connection
46
+ * // Now safe to handle requests
47
+ * ```
48
+ */
49
+ async function initializeMongoDBSchema() {
50
+ // Only run for MongoDB
51
+ if (config.DB_TYPE !== 'mongodb') {
52
+ console.log('Schema initialization skipped - not using MongoDB');
53
+ return;
54
+ }
55
+
56
+ // Check if database is connected
57
+ if (mongoose.connection.readyState !== 1) {
58
+ throw new Error(
59
+ 'Cannot initialize MongoDB schema - database not connected. ' +
60
+ 'Call connectPrisma() before initializeMongoDBSchema()'
61
+ );
62
+ }
63
+
64
+ console.log('Initializing MongoDB schema - ensuring all collections exist...');
65
+ const startTime = Date.now();
66
+
67
+ try {
68
+ // Dynamically parse Prisma schema to get collection names
69
+ const collections = getCollectionsFromSchemaSync();
70
+
71
+ if (collections.length === 0) {
72
+ console.warn('No collections found in Prisma schema - skipping initialization');
73
+ return;
74
+ }
75
+
76
+ await ensureCollectionsExist(collections);
77
+
78
+ const duration = Date.now() - startTime;
79
+ console.log(
80
+ `MongoDB schema initialization complete - ${collections.length} collections verified (${duration}ms)`
81
+ );
82
+ } catch (error) {
83
+ console.error('Failed to initialize MongoDB schema:', error.message);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get list of Prisma collection names by parsing the schema
90
+ * Useful for testing and introspection
91
+ *
92
+ * @returns {string[]} Array of collection names from Prisma schema
93
+ */
94
+ function getPrismaCollections() {
95
+ try {
96
+ return getCollectionsFromSchemaSync();
97
+ } catch (error) {
98
+ console.warn('Could not parse Prisma collections:', error.message);
99
+ return [];
100
+ }
101
+ }
102
+
103
+ module.exports = {
104
+ initializeMongoDBSchema,
105
+ getPrismaCollections,
106
+ };
@@ -0,0 +1,400 @@
1
+ const { execSync, spawn } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const chalk = require('chalk');
5
+
6
+ /**
7
+ * Prisma Command Runner Utility
8
+ * Handles execution of Prisma CLI commands for database setup
9
+ */
10
+
11
+ /**
12
+ * Gets the path to the Prisma schema file for the database type
13
+ * @param {'mongodb'|'postgresql'} dbType - Database type
14
+ * @param {string} projectRoot - Project root directory
15
+ * @returns {string} Absolute path to schema file
16
+ * @throws {Error} If schema file doesn't exist
17
+ */
18
+ function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
19
+ // Try multiple locations for the schema file
20
+ // Priority order:
21
+ // 1. Lambda layer path (where the schema actually exists in deployed Lambda)
22
+ // 2. Local node_modules (where @friggframework/core is installed - production scenario)
23
+ // 3. Parent node_modules (workspace/monorepo setup)
24
+ const possiblePaths = [
25
+ // Lambda layer path - this is where the schema actually exists in deployed Lambda
26
+ `/opt/nodejs/node_modules/generated/prisma-${dbType}/schema.prisma`,
27
+ // Check where Frigg is installed via npm (production scenario)
28
+ path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma'),
29
+ path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma')
30
+ ];
31
+
32
+ for (const schemaPath of possiblePaths) {
33
+ if (fs.existsSync(schemaPath)) {
34
+ return schemaPath;
35
+ }
36
+ }
37
+
38
+ // If not found in any location, throw error
39
+ throw new Error(
40
+ `Prisma schema not found at:\n${possiblePaths.join('\n')}\n\n` +
41
+ 'Ensure @friggframework/core is installed.'
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Runs prisma generate for the specified database type
47
+ * @param {'mongodb'|'postgresql'} dbType - Database type
48
+ * @param {boolean} verbose - Enable verbose output
49
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
50
+ */
51
+ async function runPrismaGenerate(dbType, verbose = false) {
52
+ try {
53
+ const schemaPath = getPrismaSchemaPath(dbType);
54
+
55
+ // Check if Prisma client already exists (e.g., in Lambda or pre-generated)
56
+ const generatedClientPath = path.join(path.dirname(path.dirname(schemaPath)), 'generated', `prisma-${dbType}`, 'client.js');
57
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
58
+
59
+ // In Lambda, also check the layer path (/opt/nodejs/node_modules)
60
+ const lambdaLayerClientPath = `/opt/nodejs/node_modules/generated/prisma-${dbType}/client.js`;
61
+
62
+ const clientExists = fs.existsSync(generatedClientPath) || (isLambdaEnvironment && fs.existsSync(lambdaLayerClientPath));
63
+
64
+ if (clientExists) {
65
+ const foundPath = fs.existsSync(generatedClientPath) ? generatedClientPath : lambdaLayerClientPath;
66
+ if (verbose) {
67
+ console.log(chalk.gray(`✓ Prisma client already generated at: ${foundPath}`));
68
+ }
69
+ if (isLambdaEnvironment) {
70
+ if (verbose) {
71
+ console.log(chalk.gray('Skipping generation in Lambda environment (using pre-generated client)'));
72
+ }
73
+ return {
74
+ success: true,
75
+ output: 'Using pre-generated Prisma client (Lambda environment)'
76
+ };
77
+ }
78
+ }
79
+
80
+ if (verbose) {
81
+ console.log(chalk.gray(`Running: npx prisma generate --schema=${schemaPath}`));
82
+ }
83
+
84
+ const output = execSync(
85
+ `npx prisma generate --schema=${schemaPath}`,
86
+ {
87
+ encoding: 'utf8',
88
+ stdio: verbose ? 'inherit' : 'pipe',
89
+ env: {
90
+ ...process.env,
91
+ // Suppress Prisma telemetry prompts
92
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
93
+ }
94
+ }
95
+ );
96
+
97
+ return {
98
+ success: true,
99
+ output: verbose ? 'Generated successfully' : output
100
+ };
101
+
102
+ } catch (error) {
103
+ return {
104
+ success: false,
105
+ error: error.message,
106
+ output: error.stdout?.toString() || error.stderr?.toString()
107
+ };
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Checks database migration status
113
+ * @param {'mongodb'|'postgresql'} dbType - Database type
114
+ * @returns {Promise<Object>} { upToDate: boolean, pendingMigrations?: number, error?: string }
115
+ */
116
+ async function checkDatabaseState(dbType) {
117
+ try {
118
+ // Only applicable for PostgreSQL (MongoDB uses db push)
119
+ if (dbType !== 'postgresql') {
120
+ return { upToDate: true };
121
+ }
122
+
123
+ const schemaPath = getPrismaSchemaPath(dbType);
124
+ const prismaBin = getPrismaBinaryPath();
125
+
126
+ // Use direct path instead of npx to avoid WASM file resolution issues
127
+ const isDirectBinary = prismaBin !== 'npx prisma';
128
+ const command = isDirectBinary
129
+ ? `${prismaBin} migrate status --schema=${schemaPath}`
130
+ : `npx prisma migrate status --schema=${schemaPath}`;
131
+
132
+ const output = execSync(
133
+ command,
134
+ {
135
+ encoding: 'utf8',
136
+ stdio: 'pipe',
137
+ env: {
138
+ ...process.env,
139
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
140
+ }
141
+ }
142
+ );
143
+
144
+ if (output.includes('Database schema is up to date')) {
145
+ return { upToDate: true };
146
+ }
147
+
148
+ // Parse pending migrations count
149
+ const pendingMatch = output.match(/(\d+) migration/);
150
+ const pendingMigrations = pendingMatch ? parseInt(pendingMatch[1]) : 0;
151
+
152
+ return {
153
+ upToDate: false,
154
+ pendingMigrations
155
+ };
156
+
157
+ } catch (error) {
158
+ // If migrate status fails, database might not be initialized
159
+ return {
160
+ upToDate: false,
161
+ error: error.message
162
+ };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Gets the path to the Prisma CLI entry point
168
+ *
169
+ * IMPORTANT: We invoke prisma/build/index.js directly instead of .bin/prisma
170
+ * because .bin/prisma uses __dirname to find WASM files, and when the symlink
171
+ * is resolved during Lambda packaging, __dirname points to .bin/ instead of
172
+ * prisma/build/, causing WASM files to not be found.
173
+ *
174
+ * @returns {string} Command to run Prisma CLI (e.g., 'node /path/to/index.js' or 'npx prisma')
175
+ */
176
+ function getPrismaBinaryPath() {
177
+ const fs = require('fs');
178
+
179
+ // Check function's bundled Prisma (Lambda) - use actual CLI location
180
+ const functionPrisma = '/var/task/node_modules/prisma/build/index.js';
181
+ if (fs.existsSync(functionPrisma)) {
182
+ return `node ${functionPrisma}`;
183
+ }
184
+
185
+ // Check Lambda layer path - use actual CLI location
186
+ const layerPrisma = '/opt/nodejs/node_modules/prisma/build/index.js';
187
+ if (fs.existsSync(layerPrisma)) {
188
+ return `node ${layerPrisma}`;
189
+ }
190
+
191
+ // Check local node_modules - use actual CLI location
192
+ const localPrisma = path.join(process.cwd(), 'node_modules', 'prisma', 'build', 'index.js');
193
+ if (fs.existsSync(localPrisma)) {
194
+ return `node ${localPrisma}`;
195
+ }
196
+
197
+ // Fallback to npx (local dev)
198
+ return 'npx prisma';
199
+ }
200
+
201
+ /**
202
+ * Runs Prisma migrate for PostgreSQL
203
+ * @param {'dev'|'deploy'} command - Migration command (dev or deploy)
204
+ * @param {boolean} verbose - Enable verbose output
205
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
206
+ */
207
+ async function runPrismaMigrate(command = 'dev', verbose = false) {
208
+ return new Promise((resolve) => {
209
+ try {
210
+ const schemaPath = getPrismaSchemaPath('postgresql');
211
+
212
+ // Get Prisma binary path (checks multiple locations)
213
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
214
+ const prismaBin = getPrismaBinaryPath();
215
+
216
+ // Determine args based on whether we're using direct binary or npx
217
+ // Direct binary (e.g., /var/task/node_modules/.bin/prisma): ['migrate', command, ...]
218
+ // npx (local dev or fallback): ['prisma', 'migrate', command, ...]
219
+ const isDirectBinary = prismaBin !== 'npx';
220
+ const args = isDirectBinary
221
+ ? ['migrate', command, '--schema', schemaPath]
222
+ : ['prisma', 'migrate', command, '--schema', schemaPath];
223
+
224
+ if (verbose) {
225
+ const displayCmd = isDirectBinary
226
+ ? `${prismaBin} ${args.join(' ')}`
227
+ : `npx ${args.join(' ')}`;
228
+ console.log(chalk.gray(`Running: ${displayCmd}`));
229
+ }
230
+
231
+ // Execute the command (prismaBin might be 'node /path/to/index.js' or 'npx prisma')
232
+ const [executable, ...executableArgs] = prismaBin.split(' ');
233
+ const fullArgs = [...executableArgs, ...args];
234
+
235
+ const proc = spawn(executable, fullArgs, {
236
+ stdio: 'inherit',
237
+ env: {
238
+ ...process.env,
239
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
240
+ }
241
+ });
242
+
243
+ proc.on('error', (error) => {
244
+ resolve({
245
+ success: false,
246
+ error: error.message
247
+ });
248
+ });
249
+
250
+ proc.on('close', (code) => {
251
+ if (code === 0) {
252
+ resolve({
253
+ success: true,
254
+ output: 'Migration completed successfully'
255
+ });
256
+ } else {
257
+ resolve({
258
+ success: false,
259
+ error: `Migration process exited with code ${code}`
260
+ });
261
+ }
262
+ });
263
+
264
+ } catch (error) {
265
+ resolve({
266
+ success: false,
267
+ error: error.message
268
+ });
269
+ }
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Runs Prisma db push for MongoDB
275
+ * @param {boolean} verbose - Enable verbose output
276
+ * @param {boolean} nonInteractive - Run in non-interactive mode (accepts data loss, for Lambda/CI)
277
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
278
+ */
279
+ async function runPrismaDbPush(verbose = false, nonInteractive = false) {
280
+ return new Promise((resolve) => {
281
+ try {
282
+ const schemaPath = getPrismaSchemaPath('mongodb');
283
+
284
+ const args = [
285
+ 'prisma',
286
+ 'db',
287
+ 'push',
288
+ '--schema',
289
+ schemaPath,
290
+ '--skip-generate' // We generate separately
291
+ ];
292
+
293
+ // Add non-interactive flag for Lambda/CI environments
294
+ if (nonInteractive) {
295
+ args.push('--accept-data-loss');
296
+ }
297
+
298
+ if (verbose) {
299
+ console.log(chalk.gray(`Running: npx ${args.join(' ')}`));
300
+ }
301
+
302
+ if (nonInteractive) {
303
+ console.log(chalk.yellow('⚠️ Non-interactive mode: Data loss will be automatically accepted'));
304
+ } else {
305
+ console.log(chalk.yellow('⚠️ Interactive mode: You may be prompted if schema changes cause data loss'));
306
+ }
307
+
308
+ const proc = spawn('npx', args, {
309
+ stdio: nonInteractive ? 'pipe' : 'inherit', // Use pipe for non-interactive to capture output
310
+ env: {
311
+ ...process.env,
312
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
313
+ }
314
+ });
315
+
316
+ let stdout = '';
317
+ let stderr = '';
318
+
319
+ // Capture output in non-interactive mode
320
+ if (nonInteractive) {
321
+ if (proc.stdout) {
322
+ proc.stdout.on('data', (data) => {
323
+ stdout += data.toString();
324
+ if (verbose) {
325
+ process.stdout.write(data);
326
+ }
327
+ });
328
+ }
329
+ if (proc.stderr) {
330
+ proc.stderr.on('data', (data) => {
331
+ stderr += data.toString();
332
+ if (verbose) {
333
+ process.stderr.write(data);
334
+ }
335
+ });
336
+ }
337
+ }
338
+
339
+ proc.on('error', (error) => {
340
+ resolve({
341
+ success: false,
342
+ error: error.message
343
+ });
344
+ });
345
+
346
+ proc.on('close', (code) => {
347
+ if (code === 0) {
348
+ resolve({
349
+ success: true,
350
+ output: nonInteractive ? stdout || 'Database push completed successfully' : 'Database push completed successfully'
351
+ });
352
+ } else {
353
+ resolve({
354
+ success: false,
355
+ error: `Database push process exited with code ${code}`,
356
+ output: stderr || stdout
357
+ });
358
+ }
359
+ });
360
+
361
+ } catch (error) {
362
+ resolve({
363
+ success: false,
364
+ error: error.message
365
+ });
366
+ }
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Determines migration command based on STAGE environment variable
372
+ * @param {string} stage - Stage from CLI option or environment
373
+ * @returns {'dev'|'deploy'}
374
+ */
375
+ function getMigrationCommand(stage) {
376
+ // Always use 'deploy' in Lambda environment (it's non-interactive and doesn't create migrations)
377
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
378
+ if (isLambdaEnvironment) {
379
+ return 'deploy';
380
+ }
381
+
382
+ const normalizedStage = (stage || process.env.STAGE || 'development').toLowerCase();
383
+
384
+ const developmentStages = ['dev', 'local', 'test', 'development'];
385
+
386
+ if (developmentStages.includes(normalizedStage)) {
387
+ return 'dev';
388
+ }
389
+
390
+ return 'deploy';
391
+ }
392
+
393
+ module.exports = {
394
+ getPrismaSchemaPath,
395
+ runPrismaGenerate,
396
+ checkDatabaseState,
397
+ runPrismaMigrate,
398
+ runPrismaDbPush,
399
+ getMigrationCommand
400
+ };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Prisma Schema Parser for MongoDB Collections
3
+ *
4
+ * Dynamically parses the Prisma schema file to extract MongoDB collection names.
5
+ * This ensures collection names stay in sync with the schema without hardcoding.
6
+ *
7
+ * Handles:
8
+ * - @@map() directives (custom collection names)
9
+ * - Models without @@map() (uses model name)
10
+ * - Comments and whitespace
11
+ * - Multiple schema file locations
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Parse Prisma schema file to extract collection names
19
+ *
20
+ * Reads the schema.prisma file and extracts all model definitions,
21
+ * returning the actual MongoDB collection names (from @@map directives).
22
+ *
23
+ * @param {string} schemaPath - Path to schema.prisma file
24
+ * @returns {Promise<string[]>} Array of collection names
25
+ *
26
+ * @example
27
+ * ```js
28
+ * const collections = await parseCollectionsFromSchema('./prisma/schema.prisma');
29
+ * // Returns: ['User', 'Token', 'Credential', ...]
30
+ * ```
31
+ */
32
+ async function parseCollectionsFromSchema(schemaPath) {
33
+ try {
34
+ const schemaContent = await fs.promises.readFile(schemaPath, 'utf-8');
35
+ return extractCollectionNames(schemaContent);
36
+ } catch (error) {
37
+ throw new Error(
38
+ `Failed to parse Prisma schema at ${schemaPath}: ${error.message}`
39
+ );
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Synchronous version of parseCollectionsFromSchema
45
+ *
46
+ * @param {string} schemaPath - Path to schema.prisma file
47
+ * @returns {string[]} Array of collection names
48
+ */
49
+ function parseCollectionsFromSchemaSync(schemaPath) {
50
+ try {
51
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
52
+ return extractCollectionNames(schemaContent);
53
+ } catch (error) {
54
+ throw new Error(
55
+ `Failed to parse Prisma schema at ${schemaPath}: ${error.message}`
56
+ );
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Extract collection names from Prisma schema content
62
+ *
63
+ * Parses the schema content to find:
64
+ * 1. All model definitions
65
+ * 2. Their @@map() directives (if present)
66
+ * 3. Falls back to model name if no @@map()
67
+ *
68
+ * @param {string} schemaContent - Content of schema.prisma file
69
+ * @returns {string[]} Array of collection names
70
+ * @private
71
+ */
72
+ function extractCollectionNames(schemaContent) {
73
+ const collections = [];
74
+
75
+ // Match model blocks: "model ModelName { ... }"
76
+ // Using non-greedy match to handle multiple models
77
+ const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
78
+
79
+ let match;
80
+ while ((match = modelRegex.exec(schemaContent)) !== null) {
81
+ const modelName = match[1];
82
+ const modelBody = match[2];
83
+
84
+ // Look for @@map("CollectionName") directive
85
+ const mapMatch = modelBody.match(/@@map\s*\(\s*["'](\w+)["']\s*\)/);
86
+
87
+ if (mapMatch) {
88
+ // Use mapped collection name
89
+ collections.push(mapMatch[1]);
90
+ } else {
91
+ // Use model name as collection name (Prisma default)
92
+ collections.push(modelName);
93
+ }
94
+ }
95
+
96
+ return collections;
97
+ }
98
+
99
+ /**
100
+ * Find Prisma MongoDB schema file
101
+ *
102
+ * Searches for the schema.prisma file in common locations:
103
+ * 1. prisma-mongodb/schema.prisma (Frigg convention)
104
+ * 2. prisma/schema.prisma (Prisma default)
105
+ * 3. schema.prisma (root)
106
+ *
107
+ * @param {string} startDir - Directory to start searching from
108
+ * @returns {string|null} Path to schema file, or null if not found
109
+ */
110
+ function findMongoDBSchemaFile(startDir = __dirname) {
111
+ // Start from database directory and work up
112
+ const baseDir = path.resolve(startDir, '../..');
113
+
114
+ const searchPaths = [
115
+ path.join(baseDir, 'prisma-mongodb', 'schema.prisma'),
116
+ path.join(baseDir, 'prisma', 'schema.prisma'),
117
+ path.join(baseDir, 'schema.prisma'),
118
+ ];
119
+
120
+ for (const schemaPath of searchPaths) {
121
+ if (fs.existsSync(schemaPath)) {
122
+ return schemaPath;
123
+ }
124
+ }
125
+
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Get MongoDB collection names from Prisma schema
131
+ *
132
+ * Convenience function that finds and parses the schema automatically.
133
+ *
134
+ * @returns {Promise<string[]>} Array of collection names
135
+ * @throws {Error} If schema file not found or parsing fails
136
+ *
137
+ * @example
138
+ * ```js
139
+ * const collections = await getCollectionsFromSchema();
140
+ * await ensureCollectionsExist(collections);
141
+ * ```
142
+ */
143
+ async function getCollectionsFromSchema() {
144
+ const schemaPath = findMongoDBSchemaFile();
145
+
146
+ if (!schemaPath) {
147
+ throw new Error(
148
+ 'Could not find Prisma MongoDB schema file. ' +
149
+ 'Searched: prisma-mongodb/schema.prisma, prisma/schema.prisma, schema.prisma'
150
+ );
151
+ }
152
+
153
+ return await parseCollectionsFromSchema(schemaPath);
154
+ }
155
+
156
+ /**
157
+ * Synchronous version of getCollectionsFromSchema
158
+ *
159
+ * @returns {string[]} Array of collection names
160
+ * @throws {Error} If schema file not found or parsing fails
161
+ */
162
+ function getCollectionsFromSchemaSync() {
163
+ const schemaPath = findMongoDBSchemaFile();
164
+
165
+ if (!schemaPath) {
166
+ throw new Error(
167
+ 'Could not find Prisma MongoDB schema file. ' +
168
+ 'Searched: prisma-mongodb/schema.prisma, prisma/schema.prisma, schema.prisma'
169
+ );
170
+ }
171
+
172
+ return parseCollectionsFromSchemaSync(schemaPath);
173
+ }
174
+
175
+ module.exports = {
176
+ parseCollectionsFromSchema,
177
+ parseCollectionsFromSchemaSync,
178
+ extractCollectionNames,
179
+ findMongoDBSchemaFile,
180
+ getCollectionsFromSchema,
181
+ getCollectionsFromSchemaSync,
182
+ };