@friggframework/core 2.0.0-next.8 → 2.0.0-next.80

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 (303) 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/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +79 -8
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +268 -0
  30. package/database/encryption/field-encryption-service.js +226 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +222 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -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/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/package.json +183 -0
  69. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  70. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  71. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  72. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  73. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  74. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  75. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  76. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  77. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  78. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  79. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  80. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  81. package/generated/prisma-mongodb/schema.prisma +362 -0
  82. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  84. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  85. package/generated/prisma-mongodb/wasm.js +342 -0
  86. package/generated/prisma-postgresql/client.d.ts +1 -0
  87. package/generated/prisma-postgresql/client.js +4 -0
  88. package/generated/prisma-postgresql/default.d.ts +1 -0
  89. package/generated/prisma-postgresql/default.js +4 -0
  90. package/generated/prisma-postgresql/edge.d.ts +1 -0
  91. package/generated/prisma-postgresql/edge.js +357 -0
  92. package/generated/prisma-postgresql/index-browser.js +339 -0
  93. package/generated/prisma-postgresql/index.d.ts +25131 -0
  94. package/generated/prisma-postgresql/index.js +382 -0
  95. package/generated/prisma-postgresql/package.json +183 -0
  96. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  97. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  98. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  99. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  100. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  101. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  102. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  103. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  104. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  105. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  106. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  107. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  108. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  109. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  110. package/generated/prisma-postgresql/schema.prisma +345 -0
  111. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  112. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  113. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  114. package/generated/prisma-postgresql/wasm.js +364 -0
  115. package/handlers/WEBHOOKS.md +653 -0
  116. package/handlers/app-definition-loader.js +38 -0
  117. package/handlers/app-handler-helpers.js +57 -0
  118. package/handlers/backend-utils.js +262 -0
  119. package/handlers/database-migration-handler.js +227 -0
  120. package/handlers/integration-event-dispatcher.js +54 -0
  121. package/handlers/routers/HEALTHCHECK.md +342 -0
  122. package/handlers/routers/auth.js +15 -0
  123. package/handlers/routers/db-migration.handler.js +29 -0
  124. package/handlers/routers/db-migration.js +326 -0
  125. package/handlers/routers/health.js +516 -0
  126. package/handlers/routers/integration-defined-routers.js +45 -0
  127. package/handlers/routers/integration-webhook-routers.js +67 -0
  128. package/handlers/routers/user.js +63 -0
  129. package/handlers/routers/websocket.js +57 -0
  130. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  131. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  132. package/handlers/workers/db-migration.js +352 -0
  133. package/handlers/workers/dlq-processor.js +63 -0
  134. package/handlers/workers/integration-defined-workers.js +23 -0
  135. package/index.js +82 -46
  136. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  137. package/infrastructure/scheduler/index.js +33 -0
  138. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  139. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  140. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  141. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  142. package/integrations/index.js +12 -10
  143. package/integrations/integration-base.js +364 -55
  144. package/integrations/integration-router.js +375 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +243 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +90 -0
  160. package/integrations/repositories/process-repository-mongo.js +190 -0
  161. package/integrations/repositories/process-repository-postgres.js +217 -0
  162. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  163. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  164. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  165. package/integrations/use-cases/create-integration.js +83 -0
  166. package/integrations/use-cases/create-process.js +128 -0
  167. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  168. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  169. package/integrations/use-cases/get-integration-for-user.js +78 -0
  170. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  171. package/integrations/use-cases/get-integration-instance.js +83 -0
  172. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  173. package/integrations/use-cases/get-possible-integrations.js +27 -0
  174. package/integrations/use-cases/get-process.js +87 -0
  175. package/integrations/use-cases/index.js +19 -0
  176. package/integrations/use-cases/load-integration-context.js +71 -0
  177. package/integrations/use-cases/update-integration-messages.js +44 -0
  178. package/integrations/use-cases/update-integration-status.js +32 -0
  179. package/integrations/use-cases/update-integration.js +92 -0
  180. package/integrations/use-cases/update-process-metrics.js +201 -0
  181. package/integrations/use-cases/update-process-state.js +119 -0
  182. package/integrations/utils/map-integration-dto.js +37 -0
  183. package/jest-global-setup-noop.js +3 -0
  184. package/jest-global-teardown-noop.js +3 -0
  185. package/logs/logger.js +0 -4
  186. package/{module-plugin → modules}/index.js +0 -10
  187. package/modules/module-factory.js +56 -0
  188. package/modules/module.js +256 -0
  189. package/modules/repositories/module-repository-documentdb.js +335 -0
  190. package/modules/repositories/module-repository-factory.js +40 -0
  191. package/modules/repositories/module-repository-interface.js +129 -0
  192. package/modules/repositories/module-repository-mongo.js +408 -0
  193. package/modules/repositories/module-repository-postgres.js +453 -0
  194. package/modules/repositories/module-repository.js +345 -0
  195. package/modules/requester/api-key.js +52 -0
  196. package/modules/requester/oauth-2.js +396 -0
  197. package/{module-plugin → modules}/requester/requester.js +4 -2
  198. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  199. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  200. package/modules/tests/doubles/test-module-factory.js +16 -0
  201. package/modules/tests/doubles/test-module-repository.js +39 -0
  202. package/modules/use-cases/get-entities-for-user.js +32 -0
  203. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  204. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  205. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  206. package/modules/use-cases/get-module.js +74 -0
  207. package/modules/use-cases/process-authorization-callback.js +177 -0
  208. package/modules/use-cases/refresh-entity-options.js +72 -0
  209. package/modules/use-cases/test-module-auth.js +72 -0
  210. package/modules/utils/map-module-dto.js +18 -0
  211. package/package.json +82 -50
  212. package/prisma-mongodb/schema.prisma +362 -0
  213. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  214. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  215. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  216. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  217. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  218. package/prisma-postgresql/schema.prisma +345 -0
  219. package/queues/queuer-util.js +103 -21
  220. package/syncs/manager.js +468 -443
  221. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  222. package/syncs/repositories/sync-repository-factory.js +43 -0
  223. package/syncs/repositories/sync-repository-interface.js +109 -0
  224. package/syncs/repositories/sync-repository-mongo.js +239 -0
  225. package/syncs/repositories/sync-repository-postgres.js +319 -0
  226. package/syncs/sync.js +0 -1
  227. package/token/repositories/token-repository-documentdb.js +137 -0
  228. package/token/repositories/token-repository-factory.js +40 -0
  229. package/token/repositories/token-repository-interface.js +131 -0
  230. package/token/repositories/token-repository-mongo.js +219 -0
  231. package/token/repositories/token-repository-postgres.js +264 -0
  232. package/token/repositories/token-repository.js +219 -0
  233. package/types/associations/index.d.ts +0 -17
  234. package/types/core/index.d.ts +12 -4
  235. package/types/database/index.d.ts +10 -2
  236. package/types/encrypt/index.d.ts +5 -3
  237. package/types/integrations/index.d.ts +3 -8
  238. package/types/module-plugin/index.d.ts +17 -69
  239. package/types/syncs/index.d.ts +0 -17
  240. package/user/repositories/user-repository-documentdb.js +441 -0
  241. package/user/repositories/user-repository-factory.js +52 -0
  242. package/user/repositories/user-repository-interface.js +201 -0
  243. package/user/repositories/user-repository-mongo.js +308 -0
  244. package/user/repositories/user-repository-postgres.js +360 -0
  245. package/user/tests/doubles/test-user-repository.js +72 -0
  246. package/user/use-cases/authenticate-user.js +127 -0
  247. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  248. package/user/use-cases/create-individual-user.js +61 -0
  249. package/user/use-cases/create-organization-user.js +47 -0
  250. package/user/use-cases/create-token-for-user-id.js +30 -0
  251. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  252. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  253. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  254. package/user/use-cases/login-user.js +122 -0
  255. package/user/user.js +125 -0
  256. package/utils/backend-path.js +38 -0
  257. package/utils/index.js +6 -0
  258. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  259. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  260. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  261. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  262. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  263. package/websocket/repositories/websocket-connection-repository.js +161 -0
  264. package/assertions/is-equal.js +0 -17
  265. package/associations/model.js +0 -54
  266. package/database/models/IndividualUser.js +0 -76
  267. package/database/models/OrganizationUser.js +0 -29
  268. package/database/models/State.js +0 -9
  269. package/database/models/Token.js +0 -70
  270. package/database/models/UserModel.js +0 -7
  271. package/database/models/WebsocketConnection.js +0 -49
  272. package/database/mongo.js +0 -45
  273. package/database/mongoose.js +0 -5
  274. package/encrypt/Cryptor.test.js +0 -32
  275. package/encrypt/encrypt.js +0 -132
  276. package/encrypt/encrypt.test.js +0 -1069
  277. package/encrypt/test-encrypt.js +0 -107
  278. package/errors/base-error.test.js +0 -32
  279. package/errors/fetch-error.test.js +0 -79
  280. package/errors/halt-error.test.js +0 -11
  281. package/errors/validation-errors.test.js +0 -120
  282. package/integrations/create-frigg-backend.js +0 -31
  283. package/integrations/integration-factory.js +0 -251
  284. package/integrations/integration-mapping.js +0 -43
  285. package/integrations/integration-model.js +0 -46
  286. package/integrations/integration-user.js +0 -144
  287. package/integrations/test/integration-base.test.js +0 -144
  288. package/lambda/TimeoutCatcher.test.js +0 -68
  289. package/logs/logger.test.js +0 -76
  290. package/module-plugin/auther.js +0 -393
  291. package/module-plugin/credential.js +0 -22
  292. package/module-plugin/entity-manager.js +0 -70
  293. package/module-plugin/entity.js +0 -46
  294. package/module-plugin/manager.js +0 -169
  295. package/module-plugin/module-factory.js +0 -61
  296. package/module-plugin/requester/api-key.js +0 -36
  297. package/module-plugin/requester/oauth-2.js +0 -219
  298. package/module-plugin/requester/requester.test.js +0 -28
  299. package/module-plugin/test/auther.test.js +0 -97
  300. package/syncs/model.js +0 -62
  301. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  302. /package/{module-plugin → modules}/requester/basic.js +0 -0
  303. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MongoDB Collection Utilities
3
+ *
4
+ * Provides utilities for managing MongoDB collections, particularly for
5
+ * handling the constraint that collections cannot be created inside
6
+ * multi-document transactions.
7
+ *
8
+ * Uses Prisma's $runCommandRaw to execute MongoDB admin commands.
9
+ *
10
+ * @see https://github.com/prisma/prisma/issues/8305
11
+ * @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
12
+ */
13
+
14
+ const { prisma } = require('../prisma');
15
+
16
+ /**
17
+ * Ensures a MongoDB collection exists
18
+ *
19
+ * MongoDB doesn't allow creating collections (namespaces) inside multi-document
20
+ * transactions. This function checks if a collection exists and creates it if needed,
21
+ * preventing "Cannot create namespace in multi-document transaction" errors.
22
+ *
23
+ * @param {string} collectionName - Name of the collection to ensure exists
24
+ * @returns {Promise<void>}
25
+ *
26
+ * @example
27
+ * ```js
28
+ * await ensureCollectionExists('Credential');
29
+ * // Now safe to create documents in Credential collection
30
+ * await prisma.credential.create({ data: {...} });
31
+ * ```
32
+ */
33
+ async function ensureCollectionExists(collectionName) {
34
+ try {
35
+ const result = await prisma.$runCommandRaw({
36
+ listCollections: 1,
37
+ filter: { name: collectionName },
38
+ });
39
+
40
+ const collections = result.cursor?.firstBatch || [];
41
+
42
+ if (collections.length === 0) {
43
+ await prisma.$runCommandRaw({ create: collectionName });
44
+ console.log(`Created MongoDB collection: ${collectionName}`);
45
+ }
46
+ } catch (error) {
47
+ if (error.codeName === 'NamespaceExists') {
48
+ return;
49
+ }
50
+ console.warn(`Error ensuring collection ${collectionName} exists:`, error.message);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Ensures multiple MongoDB collections exist
56
+ *
57
+ * @param {string[]} collectionNames - Array of collection names to ensure exist
58
+ * @returns {Promise<void>}
59
+ *
60
+ * @example
61
+ * ```js
62
+ * await ensureCollectionsExist(['Credential', 'User', 'Token']);
63
+ * ```
64
+ */
65
+ async function ensureCollectionsExist(collectionNames) {
66
+ await Promise.all(collectionNames.map(name => ensureCollectionExists(name)));
67
+ }
68
+
69
+ /**
70
+ * Checks if a collection exists in MongoDB
71
+ *
72
+ * @param {string} collectionName - Name of the collection to check
73
+ * @returns {Promise<boolean>} True if collection exists, false otherwise
74
+ */
75
+ async function collectionExists(collectionName) {
76
+ try {
77
+ const result = await prisma.$runCommandRaw({
78
+ listCollections: 1,
79
+ filter: { name: collectionName },
80
+ });
81
+
82
+ const collections = result.cursor?.firstBatch || [];
83
+ return collections.length > 0;
84
+ } catch (error) {
85
+ console.error(`Error checking if collection ${collectionName} exists:`, error.message);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ module.exports = {
91
+ ensureCollectionExists,
92
+ ensureCollectionsExist,
93
+ collectionExists,
94
+ };
@@ -0,0 +1,108 @@
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 { prisma } = require('../prisma');
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-compatible databases
51
+ if (config.DB_TYPE !== 'mongodb' && config.DB_TYPE !== 'documentdb') {
52
+ console.log('Schema initialization skipped - not using MongoDB-compatible database');
53
+ return;
54
+ }
55
+
56
+ // Verify database connectivity via Prisma ping
57
+ try {
58
+ await prisma.$runCommandRaw({ ping: 1 });
59
+ } catch (error) {
60
+ throw new Error(
61
+ 'Cannot initialize MongoDB schema - database not connected. ' +
62
+ 'Call connectPrisma() before initializeMongoDBSchema()'
63
+ );
64
+ }
65
+
66
+ console.log('Initializing MongoDB-compatible schema - ensuring all collections exist...');
67
+ const startTime = Date.now();
68
+
69
+ try {
70
+ // Dynamically parse Prisma schema to get collection names
71
+ const collections = getCollectionsFromSchemaSync();
72
+
73
+ if (collections.length === 0) {
74
+ console.warn('No collections found in Prisma schema - skipping initialization');
75
+ return;
76
+ }
77
+
78
+ await ensureCollectionsExist(collections);
79
+
80
+ const duration = Date.now() - startTime;
81
+ console.log(
82
+ `MongoDB-compatible schema initialization complete - ${collections.length} collections verified (${duration}ms)`
83
+ );
84
+ } catch (error) {
85
+ console.error('Failed to initialize MongoDB schema:', error.message);
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Get list of Prisma collection names by parsing the schema
92
+ * Useful for testing and introspection
93
+ *
94
+ * @returns {string[]} Array of collection names from Prisma schema
95
+ */
96
+ function getPrismaCollections() {
97
+ try {
98
+ return getCollectionsFromSchemaSync();
99
+ } catch (error) {
100
+ console.warn('Could not parse Prisma collections:', error.message);
101
+ return [];
102
+ }
103
+ }
104
+
105
+ module.exports = {
106
+ initializeMongoDBSchema,
107
+ getPrismaCollections,
108
+ };
@@ -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
+ };