@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.
Files changed (286) hide show
  1. package/CLAUDE.md +694 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/user-commands.js +283 -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 +14 -7
  12. package/credential/repositories/credential-repository-documentdb.js +304 -0
  13. package/credential/repositories/credential-repository-factory.js +54 -0
  14. package/credential/repositories/credential-repository-interface.js +98 -0
  15. package/credential/repositories/credential-repository-mongo.js +269 -0
  16. package/credential/repositories/credential-repository-postgres.js +291 -0
  17. package/credential/repositories/credential-repository.js +302 -0
  18. package/credential/use-cases/get-credential-for-user.js +25 -0
  19. package/credential/use-cases/update-authentication-status.js +15 -0
  20. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  21. package/database/adapters/lambda-invoker.js +97 -0
  22. package/database/config.js +154 -0
  23. package/database/documentdb-encryption-service.js +330 -0
  24. package/database/documentdb-utils.js +136 -0
  25. package/database/encryption/README.md +839 -0
  26. package/database/encryption/documentdb-encryption-service.md +3575 -0
  27. package/database/encryption/encryption-schema-registry.js +268 -0
  28. package/database/encryption/field-encryption-service.js +226 -0
  29. package/database/encryption/logger.js +79 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/index.js +61 -21
  32. package/database/models/WebsocketConnection.js +16 -10
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +134 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/health-check-repository.js +108 -0
  41. package/database/repositories/migration-status-repository-s3.js +137 -0
  42. package/database/use-cases/check-database-health-use-case.js +29 -0
  43. package/database/use-cases/check-database-state-use-case.js +81 -0
  44. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  45. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  46. package/database/use-cases/get-migration-status-use-case.js +93 -0
  47. package/database/use-cases/run-database-migration-use-case.js +139 -0
  48. package/database/use-cases/test-encryption-use-case.js +253 -0
  49. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  50. package/database/utils/mongodb-collection-utils.js +91 -0
  51. package/database/utils/mongodb-schema-init.js +106 -0
  52. package/database/utils/prisma-runner.js +477 -0
  53. package/database/utils/prisma-schema-parser.js +182 -0
  54. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  55. package/encrypt/Cryptor.js +34 -168
  56. package/encrypt/index.js +1 -2
  57. package/encrypt/test-encrypt.js +0 -2
  58. package/errors/client-safe-error.js +26 -0
  59. package/errors/fetch-error.js +2 -1
  60. package/errors/index.js +2 -0
  61. package/generated/prisma-mongodb/client.d.ts +1 -0
  62. package/generated/prisma-mongodb/client.js +4 -0
  63. package/generated/prisma-mongodb/default.d.ts +1 -0
  64. package/generated/prisma-mongodb/default.js +4 -0
  65. package/generated/prisma-mongodb/edge.d.ts +1 -0
  66. package/generated/prisma-mongodb/edge.js +334 -0
  67. package/generated/prisma-mongodb/index-browser.js +316 -0
  68. package/generated/prisma-mongodb/index.d.ts +22903 -0
  69. package/generated/prisma-mongodb/index.js +359 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  72. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  73. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  74. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  75. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  76. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  77. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  78. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  79. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  80. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  81. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  82. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  83. package/generated/prisma-mongodb/schema.prisma +360 -0
  84. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  85. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  86. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  87. package/generated/prisma-mongodb/wasm.js +341 -0
  88. package/generated/prisma-postgresql/client.d.ts +1 -0
  89. package/generated/prisma-postgresql/client.js +4 -0
  90. package/generated/prisma-postgresql/default.d.ts +1 -0
  91. package/generated/prisma-postgresql/default.js +4 -0
  92. package/generated/prisma-postgresql/edge.d.ts +1 -0
  93. package/generated/prisma-postgresql/edge.js +356 -0
  94. package/generated/prisma-postgresql/index-browser.js +338 -0
  95. package/generated/prisma-postgresql/index.d.ts +25077 -0
  96. package/generated/prisma-postgresql/index.js +381 -0
  97. package/generated/prisma-postgresql/package.json +183 -0
  98. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  99. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  100. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  101. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  102. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  103. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  104. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  105. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  106. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  107. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  108. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  109. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  110. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  111. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  112. package/generated/prisma-postgresql/schema.prisma +343 -0
  113. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  114. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  115. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  116. package/generated/prisma-postgresql/wasm.js +363 -0
  117. package/handlers/WEBHOOKS.md +653 -0
  118. package/handlers/app-definition-loader.js +38 -0
  119. package/handlers/app-handler-helpers.js +56 -0
  120. package/handlers/backend-utils.js +186 -0
  121. package/handlers/database-migration-handler.js +227 -0
  122. package/handlers/integration-event-dispatcher.js +54 -0
  123. package/handlers/routers/HEALTHCHECK.md +342 -0
  124. package/handlers/routers/auth.js +15 -0
  125. package/handlers/routers/db-migration.handler.js +29 -0
  126. package/handlers/routers/db-migration.js +326 -0
  127. package/handlers/routers/health.js +516 -0
  128. package/handlers/routers/integration-defined-routers.js +45 -0
  129. package/handlers/routers/integration-webhook-routers.js +67 -0
  130. package/handlers/routers/user.js +63 -0
  131. package/handlers/routers/websocket.js +57 -0
  132. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  133. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  134. package/handlers/workers/db-migration.js +352 -0
  135. package/handlers/workers/integration-defined-workers.js +27 -0
  136. package/index.js +77 -22
  137. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  138. package/integrations/index.js +12 -10
  139. package/integrations/integration-base.js +326 -55
  140. package/integrations/integration-router.js +374 -179
  141. package/integrations/options.js +1 -1
  142. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  143. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  144. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  145. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  146. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  147. package/integrations/repositories/integration-mapping-repository.js +156 -0
  148. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  149. package/integrations/repositories/integration-repository-factory.js +51 -0
  150. package/integrations/repositories/integration-repository-interface.js +127 -0
  151. package/integrations/repositories/integration-repository-mongo.js +303 -0
  152. package/integrations/repositories/integration-repository-postgres.js +352 -0
  153. package/integrations/repositories/process-repository-documentdb.js +243 -0
  154. package/integrations/repositories/process-repository-factory.js +53 -0
  155. package/integrations/repositories/process-repository-interface.js +90 -0
  156. package/integrations/repositories/process-repository-mongo.js +190 -0
  157. package/integrations/repositories/process-repository-postgres.js +217 -0
  158. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  159. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  160. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  161. package/integrations/use-cases/create-integration.js +83 -0
  162. package/integrations/use-cases/create-process.js +128 -0
  163. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  164. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  165. package/integrations/use-cases/get-integration-for-user.js +78 -0
  166. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  167. package/integrations/use-cases/get-integration-instance.js +83 -0
  168. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  169. package/integrations/use-cases/get-possible-integrations.js +27 -0
  170. package/integrations/use-cases/get-process.js +87 -0
  171. package/integrations/use-cases/index.js +19 -0
  172. package/integrations/use-cases/load-integration-context.js +71 -0
  173. package/integrations/use-cases/update-integration-messages.js +44 -0
  174. package/integrations/use-cases/update-integration-status.js +32 -0
  175. package/integrations/use-cases/update-integration.js +92 -0
  176. package/integrations/use-cases/update-process-metrics.js +201 -0
  177. package/integrations/use-cases/update-process-state.js +119 -0
  178. package/integrations/utils/map-integration-dto.js +37 -0
  179. package/jest-global-setup-noop.js +3 -0
  180. package/jest-global-teardown-noop.js +3 -0
  181. package/logs/logger.js +0 -4
  182. package/{module-plugin → modules}/entity.js +1 -1
  183. package/{module-plugin → modules}/index.js +0 -8
  184. package/modules/module-factory.js +56 -0
  185. package/modules/module.js +221 -0
  186. package/modules/repositories/module-repository-documentdb.js +307 -0
  187. package/modules/repositories/module-repository-factory.js +40 -0
  188. package/modules/repositories/module-repository-interface.js +129 -0
  189. package/modules/repositories/module-repository-mongo.js +377 -0
  190. package/modules/repositories/module-repository-postgres.js +426 -0
  191. package/modules/repositories/module-repository.js +316 -0
  192. package/modules/requester/api-key.js +52 -0
  193. package/{module-plugin → modules}/requester/requester.js +1 -0
  194. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  195. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  196. package/modules/tests/doubles/test-module-factory.js +16 -0
  197. package/modules/tests/doubles/test-module-repository.js +39 -0
  198. package/modules/use-cases/get-entities-for-user.js +32 -0
  199. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  200. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  201. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  202. package/modules/use-cases/get-module.js +74 -0
  203. package/modules/use-cases/process-authorization-callback.js +133 -0
  204. package/modules/use-cases/refresh-entity-options.js +72 -0
  205. package/modules/use-cases/test-module-auth.js +72 -0
  206. package/modules/utils/map-module-dto.js +18 -0
  207. package/package.json +82 -50
  208. package/prisma-mongodb/schema.prisma +360 -0
  209. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  210. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  211. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  212. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  213. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  214. package/prisma-postgresql/schema.prisma +343 -0
  215. package/queues/queuer-util.js +27 -22
  216. package/syncs/manager.js +468 -443
  217. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  218. package/syncs/repositories/sync-repository-factory.js +43 -0
  219. package/syncs/repositories/sync-repository-interface.js +109 -0
  220. package/syncs/repositories/sync-repository-mongo.js +239 -0
  221. package/syncs/repositories/sync-repository-postgres.js +319 -0
  222. package/syncs/sync.js +0 -1
  223. package/token/repositories/token-repository-documentdb.js +137 -0
  224. package/token/repositories/token-repository-factory.js +40 -0
  225. package/token/repositories/token-repository-interface.js +131 -0
  226. package/token/repositories/token-repository-mongo.js +219 -0
  227. package/token/repositories/token-repository-postgres.js +264 -0
  228. package/token/repositories/token-repository.js +219 -0
  229. package/types/core/index.d.ts +2 -2
  230. package/types/integrations/index.d.ts +2 -6
  231. package/types/module-plugin/index.d.ts +5 -59
  232. package/types/syncs/index.d.ts +0 -2
  233. package/user/repositories/user-repository-documentdb.js +441 -0
  234. package/user/repositories/user-repository-factory.js +52 -0
  235. package/user/repositories/user-repository-interface.js +201 -0
  236. package/user/repositories/user-repository-mongo.js +308 -0
  237. package/user/repositories/user-repository-postgres.js +360 -0
  238. package/user/tests/doubles/test-user-repository.js +72 -0
  239. package/user/use-cases/authenticate-user.js +127 -0
  240. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  241. package/user/use-cases/create-individual-user.js +61 -0
  242. package/user/use-cases/create-organization-user.js +47 -0
  243. package/user/use-cases/create-token-for-user-id.js +30 -0
  244. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  245. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  246. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  247. package/user/use-cases/login-user.js +122 -0
  248. package/user/user.js +125 -0
  249. package/utils/backend-path.js +38 -0
  250. package/utils/index.js +6 -0
  251. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  252. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  253. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  254. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  255. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  256. package/websocket/repositories/websocket-connection-repository.js +161 -0
  257. package/database/models/State.js +0 -9
  258. package/database/models/Token.js +0 -70
  259. package/database/mongo.js +0 -45
  260. package/encrypt/Cryptor.test.js +0 -32
  261. package/encrypt/encrypt.js +0 -132
  262. package/encrypt/encrypt.test.js +0 -1069
  263. package/errors/base-error.test.js +0 -32
  264. package/errors/fetch-error.test.js +0 -79
  265. package/errors/halt-error.test.js +0 -11
  266. package/errors/validation-errors.test.js +0 -120
  267. package/integrations/create-frigg-backend.js +0 -31
  268. package/integrations/integration-factory.js +0 -251
  269. package/integrations/integration-mapping.js +0 -43
  270. package/integrations/integration-model.js +0 -46
  271. package/integrations/integration-user.js +0 -144
  272. package/integrations/test/integration-base.test.js +0 -144
  273. package/lambda/TimeoutCatcher.test.js +0 -68
  274. package/logs/logger.test.js +0 -76
  275. package/module-plugin/auther.js +0 -393
  276. package/module-plugin/credential.js +0 -22
  277. package/module-plugin/entity-manager.js +0 -70
  278. package/module-plugin/manager.js +0 -169
  279. package/module-plugin/module-factory.js +0 -61
  280. package/module-plugin/requester/api-key.js +0 -36
  281. package/module-plugin/requester/requester.test.js +0 -28
  282. package/module-plugin/test/auther.test.js +0 -97
  283. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  284. /package/{module-plugin → modules}/requester/basic.js +0 -0
  285. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  286. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,477 @@
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'|'documentdb'} 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 normalizeMongoCompatible(dbType) {
19
+ return dbType === 'documentdb' ? 'mongodb' : dbType;
20
+ }
21
+
22
+ function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
23
+ const normalizedType = normalizeMongoCompatible(dbType);
24
+ // Try multiple locations for the schema file
25
+ // Priority order:
26
+ // 1. Lambda layer path (where the schema actually exists in deployed Lambda)
27
+ // 2. Local node_modules (where @friggframework/core is installed - production scenario)
28
+ // 3. Parent node_modules (workspace/monorepo setup)
29
+ const possiblePaths = [
30
+ // Lambda layer path - this is where the schema actually exists in deployed Lambda
31
+ `/opt/nodejs/node_modules/generated/prisma-${normalizedType}/schema.prisma`,
32
+ // Check where Frigg is installed via npm (production scenario)
33
+ path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${normalizedType}`, 'schema.prisma'),
34
+ path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${normalizedType}`, 'schema.prisma')
35
+ ];
36
+
37
+ for (const schemaPath of possiblePaths) {
38
+ if (fs.existsSync(schemaPath)) {
39
+ return schemaPath;
40
+ }
41
+ }
42
+
43
+ // If not found in any location, throw error
44
+ throw new Error(
45
+ `Prisma schema not found at:\n${possiblePaths.join('\n')}\n\n` +
46
+ 'Ensure @friggframework/core is installed.'
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Runs prisma generate for the specified database type
52
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
53
+ * @param {boolean} verbose - Enable verbose output
54
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
55
+ */
56
+ async function runPrismaGenerate(dbType, verbose = false) {
57
+ try {
58
+ const schemaPath = getPrismaSchemaPath(dbType);
59
+
60
+ // Check if Prisma client already exists (e.g., in Lambda or pre-generated)
61
+ const normalizedType = normalizeMongoCompatible(dbType);
62
+ const generatedClientPath = path.join(path.dirname(path.dirname(schemaPath)), 'generated', `prisma-${normalizedType}`, 'client.js');
63
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
64
+
65
+ // In Lambda, also check the layer path (/opt/nodejs/node_modules)
66
+ const lambdaLayerClientPath = `/opt/nodejs/node_modules/generated/prisma-${normalizedType}/client.js`;
67
+
68
+ const clientExists = fs.existsSync(generatedClientPath) || (isLambdaEnvironment && fs.existsSync(lambdaLayerClientPath));
69
+
70
+ if (clientExists) {
71
+ const foundPath = fs.existsSync(generatedClientPath) ? generatedClientPath : lambdaLayerClientPath;
72
+ if (verbose) {
73
+ console.log(chalk.gray(`✓ Prisma client already generated at: ${foundPath}`));
74
+ }
75
+ if (isLambdaEnvironment) {
76
+ if (verbose) {
77
+ console.log(chalk.gray('Skipping generation in Lambda environment (using pre-generated client)'));
78
+ }
79
+ return {
80
+ success: true,
81
+ output: 'Using pre-generated Prisma client (Lambda environment)'
82
+ };
83
+ }
84
+ }
85
+
86
+ if (verbose) {
87
+ console.log(chalk.gray(`Running: npx prisma generate --schema=${schemaPath}`));
88
+ }
89
+
90
+ const output = execSync(
91
+ `npx prisma generate --schema=${schemaPath}`,
92
+ {
93
+ encoding: 'utf8',
94
+ stdio: verbose ? 'inherit' : 'pipe',
95
+ env: {
96
+ ...process.env,
97
+ // Suppress Prisma telemetry prompts
98
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
99
+ }
100
+ }
101
+ );
102
+
103
+ return {
104
+ success: true,
105
+ output: verbose ? 'Generated successfully' : output
106
+ };
107
+
108
+ } catch (error) {
109
+ return {
110
+ success: false,
111
+ error: error.message,
112
+ output: error.stdout?.toString() || error.stderr?.toString()
113
+ };
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Checks database migration status
119
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
120
+ * @returns {Promise<Object>} { upToDate: boolean, pendingMigrations?: number, error?: string }
121
+ */
122
+ async function checkDatabaseState(dbType) {
123
+ try {
124
+ // Only applicable for PostgreSQL (MongoDB uses db push)
125
+ if (dbType !== 'postgresql') {
126
+ return { upToDate: true };
127
+ }
128
+
129
+ const schemaPath = getPrismaSchemaPath(dbType);
130
+ const prismaBin = getPrismaBinaryPath();
131
+
132
+ // Use direct path instead of npx to avoid WASM file resolution issues
133
+ const isDirectBinary = prismaBin !== 'npx prisma';
134
+ const command = isDirectBinary
135
+ ? `${prismaBin} migrate status --schema=${schemaPath}`
136
+ : `npx prisma migrate status --schema=${schemaPath}`;
137
+
138
+ const output = execSync(
139
+ command,
140
+ {
141
+ encoding: 'utf8',
142
+ stdio: 'pipe',
143
+ env: {
144
+ ...process.env,
145
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
146
+ }
147
+ }
148
+ );
149
+
150
+ if (output.includes('Database schema is up to date')) {
151
+ return { upToDate: true };
152
+ }
153
+
154
+ // Parse pending migrations count
155
+ const pendingMatch = output.match(/(\d+) migration/);
156
+ const pendingMigrations = pendingMatch ? parseInt(pendingMatch[1]) : 0;
157
+
158
+ return {
159
+ upToDate: false,
160
+ pendingMigrations
161
+ };
162
+
163
+ } catch (error) {
164
+ // If migrate status fails, database might not be initialized
165
+ return {
166
+ upToDate: false,
167
+ error: error.message
168
+ };
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Gets the path to the Prisma CLI entry point
174
+ *
175
+ * IMPORTANT: We invoke prisma/build/index.js directly instead of .bin/prisma
176
+ * because .bin/prisma uses __dirname to find WASM files, and when the symlink
177
+ * is resolved during Lambda packaging, __dirname points to .bin/ instead of
178
+ * prisma/build/, causing WASM files to not be found.
179
+ *
180
+ * @returns {string} Command to run Prisma CLI (e.g., 'node /path/to/index.js' or 'npx prisma')
181
+ */
182
+ function getPrismaBinaryPath() {
183
+ const fs = require('fs');
184
+
185
+ // Check function's bundled Prisma (Lambda) - use actual CLI location
186
+ const functionPrisma = '/var/task/node_modules/prisma/build/index.js';
187
+ if (fs.existsSync(functionPrisma)) {
188
+ return `node ${functionPrisma}`;
189
+ }
190
+
191
+ // Check Lambda layer path - use actual CLI location
192
+ const layerPrisma = '/opt/nodejs/node_modules/prisma/build/index.js';
193
+ if (fs.existsSync(layerPrisma)) {
194
+ return `node ${layerPrisma}`;
195
+ }
196
+
197
+ // Check local node_modules - use actual CLI location
198
+ const localPrisma = path.join(process.cwd(), 'node_modules', 'prisma', 'build', 'index.js');
199
+ if (fs.existsSync(localPrisma)) {
200
+ return `node ${localPrisma}`;
201
+ }
202
+
203
+ // Fallback to npx (local dev)
204
+ return 'npx prisma';
205
+ }
206
+
207
+ /**
208
+ * Runs Prisma migrate for PostgreSQL
209
+ * @param {'dev'|'deploy'} command - Migration command (dev or deploy)
210
+ * @param {boolean} verbose - Enable verbose output
211
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
212
+ */
213
+ async function runPrismaMigrate(command = 'dev', verbose = false) {
214
+ return new Promise((resolve) => {
215
+ try {
216
+ const schemaPath = getPrismaSchemaPath('postgresql');
217
+
218
+ // Get Prisma binary path (checks multiple locations)
219
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
220
+ const prismaBin = getPrismaBinaryPath();
221
+
222
+ // Determine args based on whether we're using direct binary or npx
223
+ // Direct binary (e.g., /var/task/node_modules/.bin/prisma): ['migrate', command, ...]
224
+ // npx (local dev or fallback): ['prisma', 'migrate', command, ...]
225
+ const isDirectBinary = prismaBin !== 'npx';
226
+ const args = isDirectBinary
227
+ ? ['migrate', command, '--schema', schemaPath]
228
+ : ['prisma', 'migrate', command, '--schema', schemaPath];
229
+
230
+ if (verbose) {
231
+ const displayCmd = isDirectBinary
232
+ ? `${prismaBin} ${args.join(' ')}`
233
+ : `npx ${args.join(' ')}`;
234
+ console.log(chalk.gray(`Running: ${displayCmd}`));
235
+ }
236
+
237
+ // Execute the command (prismaBin might be 'node /path/to/index.js' or 'npx prisma')
238
+ const [executable, ...executableArgs] = prismaBin.split(' ');
239
+ const fullArgs = [...executableArgs, ...args];
240
+
241
+ const proc = spawn(executable, fullArgs, {
242
+ stdio: 'inherit',
243
+ env: {
244
+ ...process.env,
245
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
246
+ }
247
+ });
248
+
249
+ proc.on('error', (error) => {
250
+ resolve({
251
+ success: false,
252
+ error: error.message
253
+ });
254
+ });
255
+
256
+ proc.on('close', (code) => {
257
+ if (code === 0) {
258
+ resolve({
259
+ success: true,
260
+ output: 'Migration completed successfully'
261
+ });
262
+ } else {
263
+ resolve({
264
+ success: false,
265
+ error: `Migration process exited with code ${code}`
266
+ });
267
+ }
268
+ });
269
+
270
+ } catch (error) {
271
+ resolve({
272
+ success: false,
273
+ error: error.message
274
+ });
275
+ }
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Runs Prisma db push for MongoDB
281
+ * @param {boolean} verbose - Enable verbose output
282
+ * @param {boolean} nonInteractive - Run in non-interactive mode (accepts data loss, for Lambda/CI)
283
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
284
+ */
285
+ async function runPrismaDbPush(verbose = false, nonInteractive = false) {
286
+ return new Promise((resolve) => {
287
+ try {
288
+ const schemaPath = getPrismaSchemaPath('mongodb');
289
+
290
+ const args = [
291
+ 'prisma',
292
+ 'db',
293
+ 'push',
294
+ '--schema',
295
+ schemaPath,
296
+ '--skip-generate' // We generate separately
297
+ ];
298
+
299
+ // Add non-interactive flag for Lambda/CI environments
300
+ if (nonInteractive) {
301
+ args.push('--accept-data-loss');
302
+ }
303
+
304
+ if (verbose) {
305
+ console.log(chalk.gray(`Running: npx ${args.join(' ')}`));
306
+ }
307
+
308
+ if (nonInteractive) {
309
+ console.log(chalk.yellow('⚠️ Non-interactive mode: Data loss will be automatically accepted'));
310
+ } else {
311
+ console.log(chalk.yellow('⚠️ Interactive mode: You may be prompted if schema changes cause data loss'));
312
+ }
313
+
314
+ const proc = spawn('npx', args, {
315
+ stdio: nonInteractive ? 'pipe' : 'inherit', // Use pipe for non-interactive to capture output
316
+ env: {
317
+ ...process.env,
318
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
319
+ }
320
+ });
321
+
322
+ let stdout = '';
323
+ let stderr = '';
324
+
325
+ // Capture output in non-interactive mode
326
+ if (nonInteractive) {
327
+ if (proc.stdout) {
328
+ proc.stdout.on('data', (data) => {
329
+ stdout += data.toString();
330
+ if (verbose) {
331
+ process.stdout.write(data);
332
+ }
333
+ });
334
+ }
335
+ if (proc.stderr) {
336
+ proc.stderr.on('data', (data) => {
337
+ stderr += data.toString();
338
+ if (verbose) {
339
+ process.stderr.write(data);
340
+ }
341
+ });
342
+ }
343
+ }
344
+
345
+ proc.on('error', (error) => {
346
+ resolve({
347
+ success: false,
348
+ error: error.message
349
+ });
350
+ });
351
+
352
+ proc.on('close', (code) => {
353
+ if (code === 0) {
354
+ resolve({
355
+ success: true,
356
+ output: nonInteractive ? stdout || 'Database push completed successfully' : 'Database push completed successfully'
357
+ });
358
+ } else {
359
+ resolve({
360
+ success: false,
361
+ error: `Database push process exited with code ${code}`,
362
+ output: stderr || stdout
363
+ });
364
+ }
365
+ });
366
+
367
+ } catch (error) {
368
+ resolve({
369
+ success: false,
370
+ error: error.message
371
+ });
372
+ }
373
+ });
374
+ }
375
+
376
+ /**
377
+ * Runs Prisma migrate resolve to mark a migration as applied or rolled back
378
+ * @param {string} migrationName - Name of the migration to resolve (e.g., '20251112195422_update_user_unique_constraints')
379
+ * @param {'applied'|'rolled-back'} action - Whether to mark as applied or rolled back
380
+ * @param {boolean} verbose - Enable verbose output
381
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
382
+ */
383
+ async function runPrismaMigrateResolve(migrationName, action = 'applied', verbose = false) {
384
+ return new Promise((resolve) => {
385
+ try {
386
+ const schemaPath = getPrismaSchemaPath('postgresql');
387
+
388
+ // Get Prisma binary path (checks multiple locations)
389
+ const prismaBin = getPrismaBinaryPath();
390
+
391
+ // Determine args based on whether we're using direct binary or npx
392
+ const isDirectBinary = prismaBin !== 'npx prisma';
393
+ const args = isDirectBinary
394
+ ? ['migrate', 'resolve', `--${action}`, migrationName, '--schema', schemaPath]
395
+ : ['prisma', 'migrate', 'resolve', `--${action}`, migrationName, '--schema', schemaPath];
396
+
397
+ if (verbose) {
398
+ const displayCmd = isDirectBinary
399
+ ? `${prismaBin} ${args.join(' ')}`
400
+ : `npx ${args.join(' ')}`;
401
+ console.log(chalk.gray(`Running: ${displayCmd}`));
402
+ }
403
+
404
+ // Execute the command (prismaBin might be 'node /path/to/index.js' or 'npx prisma')
405
+ const [executable, ...executableArgs] = prismaBin.split(' ');
406
+ const fullArgs = [...executableArgs, ...args];
407
+
408
+ const proc = spawn(executable, fullArgs, {
409
+ stdio: 'inherit',
410
+ env: {
411
+ ...process.env,
412
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
413
+ }
414
+ });
415
+
416
+ proc.on('error', (error) => {
417
+ resolve({
418
+ success: false,
419
+ error: error.message
420
+ });
421
+ });
422
+
423
+ proc.on('close', (code) => {
424
+ if (code === 0) {
425
+ resolve({
426
+ success: true,
427
+ output: `Migration ${migrationName} marked as ${action}`
428
+ });
429
+ } else {
430
+ resolve({
431
+ success: false,
432
+ error: `Resolve process exited with code ${code}`
433
+ });
434
+ }
435
+ });
436
+
437
+ } catch (error) {
438
+ resolve({
439
+ success: false,
440
+ error: error.message
441
+ });
442
+ }
443
+ });
444
+ }
445
+
446
+ /**
447
+ * Determines migration command based on STAGE environment variable
448
+ * @param {string} stage - Stage from CLI option or environment
449
+ * @returns {'dev'|'deploy'}
450
+ */
451
+ function getMigrationCommand(stage) {
452
+ // Always use 'deploy' in Lambda environment (it's non-interactive and doesn't create migrations)
453
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
454
+ if (isLambdaEnvironment) {
455
+ return 'deploy';
456
+ }
457
+
458
+ const normalizedStage = (stage || process.env.STAGE || 'development').toLowerCase();
459
+
460
+ const developmentStages = ['dev', 'local', 'test', 'development'];
461
+
462
+ if (developmentStages.includes(normalizedStage)) {
463
+ return 'dev';
464
+ }
465
+
466
+ return 'deploy';
467
+ }
468
+
469
+ module.exports = {
470
+ getPrismaSchemaPath,
471
+ runPrismaGenerate,
472
+ checkDatabaseState,
473
+ runPrismaMigrate,
474
+ runPrismaMigrateResolve,
475
+ runPrismaDbPush,
476
+ getMigrationCommand
477
+ };
@@ -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
+ };