@friggframework/core 2.0.0-next.10 → 2.0.0-next.101

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 (344) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +166 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +228 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +268 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +358 -0
  16. package/application/commands/credential-commands.js +245 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +343 -0
  19. package/application/commands/process-commands.js +135 -0
  20. package/application/commands/scheduler-commands.js +263 -0
  21. package/application/commands/user-commands.js +312 -0
  22. package/application/index.js +82 -0
  23. package/assertions/index.js +0 -3
  24. package/core/CLAUDE.md +690 -0
  25. package/core/Worker.js +60 -24
  26. package/core/create-handler.js +84 -6
  27. package/credential/repositories/credential-repository-documentdb.js +304 -0
  28. package/credential/repositories/credential-repository-factory.js +54 -0
  29. package/credential/repositories/credential-repository-interface.js +98 -0
  30. package/credential/repositories/credential-repository-mongo.js +269 -0
  31. package/credential/repositories/credential-repository-postgres.js +287 -0
  32. package/credential/repositories/credential-repository.js +300 -0
  33. package/credential/use-cases/get-credential-for-user.js +25 -0
  34. package/credential/use-cases/update-authentication-status.js +15 -0
  35. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  36. package/database/adapters/lambda-invoker.js +97 -0
  37. package/database/config.js +154 -0
  38. package/database/documentdb-encryption-service.js +330 -0
  39. package/database/documentdb-utils.js +136 -0
  40. package/database/encryption/README.md +839 -0
  41. package/database/encryption/documentdb-encryption-service.md +3575 -0
  42. package/database/encryption/encryption-schema-registry.js +401 -0
  43. package/database/encryption/field-encryption-service.js +254 -0
  44. package/database/encryption/logger.js +79 -0
  45. package/database/encryption/prisma-encryption-extension.js +230 -0
  46. package/database/index.js +21 -21
  47. package/database/prisma.js +182 -0
  48. package/database/repositories/health-check-repository-documentdb.js +138 -0
  49. package/database/repositories/health-check-repository-factory.js +48 -0
  50. package/database/repositories/health-check-repository-interface.js +82 -0
  51. package/database/repositories/health-check-repository-mongodb.js +89 -0
  52. package/database/repositories/health-check-repository-postgres.js +82 -0
  53. package/database/repositories/migration-status-repository-s3.js +137 -0
  54. package/database/use-cases/check-database-health-use-case.js +29 -0
  55. package/database/use-cases/check-database-state-use-case.js +81 -0
  56. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  57. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  58. package/database/use-cases/get-migration-status-use-case.js +93 -0
  59. package/database/use-cases/run-database-migration-use-case.js +139 -0
  60. package/database/use-cases/test-encryption-use-case.js +253 -0
  61. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  62. package/database/utils/mongodb-collection-utils.js +94 -0
  63. package/database/utils/mongodb-schema-init.js +108 -0
  64. package/database/utils/prisma-runner.js +477 -0
  65. package/database/utils/prisma-schema-parser.js +182 -0
  66. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  67. package/encrypt/Cryptor.js +34 -168
  68. package/encrypt/index.js +1 -2
  69. package/errors/client-safe-error.js +26 -0
  70. package/errors/fetch-error.js +15 -7
  71. package/errors/index.js +2 -0
  72. package/generated/prisma-mongodb/client.d.ts +1 -0
  73. package/generated/prisma-mongodb/client.js +5 -0
  74. package/generated/prisma-mongodb/default.d.ts +1 -0
  75. package/generated/prisma-mongodb/default.js +5 -0
  76. package/generated/prisma-mongodb/edge.d.ts +1 -0
  77. package/generated/prisma-mongodb/edge.js +374 -0
  78. package/generated/prisma-mongodb/index-browser.js +356 -0
  79. package/generated/prisma-mongodb/index.d.ts +25667 -0
  80. package/generated/prisma-mongodb/index.js +399 -0
  81. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  82. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  83. package/generated/prisma-mongodb/package.json +183 -0
  84. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  85. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  86. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  87. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  88. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  89. package/generated/prisma-mongodb/runtime/library.js +147 -0
  90. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  91. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  92. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  93. package/generated/prisma-mongodb/schema.prisma +432 -0
  94. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  95. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  96. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  97. package/generated/prisma-mongodb/wasm.js +381 -0
  98. package/generated/prisma-postgresql/client.d.ts +1 -0
  99. package/generated/prisma-postgresql/client.js +5 -0
  100. package/generated/prisma-postgresql/default.d.ts +1 -0
  101. package/generated/prisma-postgresql/default.js +5 -0
  102. package/generated/prisma-postgresql/edge.d.ts +1 -0
  103. package/generated/prisma-postgresql/edge.js +395 -0
  104. package/generated/prisma-postgresql/index-browser.js +377 -0
  105. package/generated/prisma-postgresql/index.d.ts +28455 -0
  106. package/generated/prisma-postgresql/index.js +420 -0
  107. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  108. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  109. package/generated/prisma-postgresql/package.json +183 -0
  110. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  111. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  112. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  113. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  114. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  115. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  116. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  117. package/generated/prisma-postgresql/runtime/library.js +147 -0
  118. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  119. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  120. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  121. package/generated/prisma-postgresql/schema.prisma +414 -0
  122. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  123. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  124. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  125. package/generated/prisma-postgresql/wasm.js +402 -0
  126. package/handlers/WEBHOOKS.md +653 -0
  127. package/handlers/app-definition-loader.js +43 -0
  128. package/handlers/app-handler-helpers.js +3 -3
  129. package/handlers/backend-utils.js +271 -42
  130. package/handlers/database-migration-handler.js +227 -0
  131. package/handlers/integration-event-dispatcher.js +54 -0
  132. package/handlers/middleware/admin-auth.js +73 -0
  133. package/handlers/routers/HEALTHCHECK.md +342 -0
  134. package/handlers/routers/auth.js +4 -15
  135. package/handlers/routers/db-migration.handler.js +29 -0
  136. package/handlers/routers/db-migration.js +311 -0
  137. package/handlers/routers/health.js +518 -0
  138. package/handlers/routers/integration-defined-routers.js +85 -10
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +27 -5
  142. package/handlers/routers/websocket.js +5 -3
  143. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  144. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  145. package/handlers/workers/db-migration.js +352 -0
  146. package/handlers/workers/dlq-processor.js +63 -0
  147. package/handlers/workers/integration-defined-workers.js +13 -7
  148. package/index.js +91 -46
  149. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  150. package/infrastructure/scheduler/index.js +33 -0
  151. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  152. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  153. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  154. package/integrations/EXTENSIONS.md +240 -0
  155. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  156. package/integrations/extension.js +254 -0
  157. package/integrations/index.js +20 -10
  158. package/integrations/integration-base.js +633 -72
  159. package/integrations/integration-router.js +398 -180
  160. package/integrations/options.js +1 -1
  161. package/integrations/repositories/config-patch-shared.js +43 -0
  162. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  163. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  164. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  165. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  166. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  167. package/integrations/repositories/integration-mapping-repository.js +156 -0
  168. package/integrations/repositories/integration-repository-documentdb.js +284 -0
  169. package/integrations/repositories/integration-repository-factory.js +51 -0
  170. package/integrations/repositories/integration-repository-interface.js +173 -0
  171. package/integrations/repositories/integration-repository-mongo.js +414 -0
  172. package/integrations/repositories/integration-repository-postgres.js +452 -0
  173. package/integrations/repositories/process-repository-documentdb.js +311 -0
  174. package/integrations/repositories/process-repository-factory.js +53 -0
  175. package/integrations/repositories/process-repository-interface.js +136 -0
  176. package/integrations/repositories/process-repository-mongo.js +262 -0
  177. package/integrations/repositories/process-repository-postgres.js +380 -0
  178. package/integrations/repositories/process-update-ops-shared.js +112 -0
  179. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  180. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  181. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  182. package/integrations/use-cases/create-integration.js +215 -0
  183. package/integrations/use-cases/create-process.js +130 -0
  184. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  185. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  186. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  187. package/integrations/use-cases/get-integration-for-user.js +78 -0
  188. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  189. package/integrations/use-cases/get-integration-instance.js +83 -0
  190. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  191. package/integrations/use-cases/get-possible-integrations.js +27 -0
  192. package/integrations/use-cases/get-process.js +89 -0
  193. package/integrations/use-cases/index.js +19 -0
  194. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  195. package/integrations/use-cases/load-integration-context.js +71 -0
  196. package/integrations/use-cases/patch-integration-config.js +39 -0
  197. package/integrations/use-cases/process-errors.js +28 -0
  198. package/integrations/use-cases/update-integration-config.js +32 -0
  199. package/integrations/use-cases/update-integration-messages.js +44 -0
  200. package/integrations/use-cases/update-integration-status.js +32 -0
  201. package/integrations/use-cases/update-integration.js +92 -0
  202. package/integrations/use-cases/update-process-metrics.js +216 -0
  203. package/integrations/use-cases/update-process-state.js +163 -0
  204. package/integrations/utils/map-integration-dto.js +37 -0
  205. package/jest-global-setup-noop.js +3 -0
  206. package/jest-global-teardown-noop.js +3 -0
  207. package/logs/logger.js +0 -4
  208. package/{module-plugin → modules}/index.js +0 -10
  209. package/modules/module-factory.js +56 -0
  210. package/modules/module.js +287 -0
  211. package/modules/repositories/module-repository-documentdb.js +350 -0
  212. package/modules/repositories/module-repository-factory.js +40 -0
  213. package/modules/repositories/module-repository-interface.js +145 -0
  214. package/modules/repositories/module-repository-mongo.js +436 -0
  215. package/modules/repositories/module-repository-postgres.js +481 -0
  216. package/modules/repositories/module-repository.js +369 -0
  217. package/modules/requester/api-key.js +52 -0
  218. package/modules/requester/oauth-2.js +396 -0
  219. package/modules/requester/requester.js +334 -0
  220. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  221. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  222. package/modules/tests/doubles/test-module-factory.js +16 -0
  223. package/modules/tests/doubles/test-module-repository.js +39 -0
  224. package/modules/use-cases/get-entities-for-user.js +32 -0
  225. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  226. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  227. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  228. package/modules/use-cases/get-module.js +74 -0
  229. package/modules/use-cases/process-authorization-callback.js +243 -0
  230. package/modules/use-cases/refresh-entity-options.js +72 -0
  231. package/modules/use-cases/test-module-auth.js +72 -0
  232. package/modules/utils/map-module-dto.js +18 -0
  233. package/package.json +82 -50
  234. package/prisma-mongodb/schema.prisma +432 -0
  235. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  236. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  237. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  238. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  239. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  240. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  241. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  242. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  243. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  244. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  245. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  246. package/prisma-postgresql/schema.prisma +414 -0
  247. package/queues/queuer-util.js +103 -21
  248. package/reporting/README.md +86 -0
  249. package/reporting/index.js +17 -0
  250. package/reporting/reporting-router.js +77 -0
  251. package/reporting/repositories/reporting-repository-documentdb.js +127 -0
  252. package/reporting/repositories/reporting-repository-factory.js +35 -0
  253. package/reporting/repositories/reporting-repository-interface.js +16 -0
  254. package/reporting/repositories/reporting-repository-mongo.js +54 -0
  255. package/reporting/repositories/reporting-repository-postgres.js +70 -0
  256. package/reporting/use-cases/index.js +6 -0
  257. package/reporting/use-cases/list-integrations-report.js +142 -0
  258. package/syncs/manager.js +468 -443
  259. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  260. package/syncs/repositories/sync-repository-factory.js +43 -0
  261. package/syncs/repositories/sync-repository-interface.js +109 -0
  262. package/syncs/repositories/sync-repository-mongo.js +239 -0
  263. package/syncs/repositories/sync-repository-postgres.js +319 -0
  264. package/syncs/sync.js +0 -1
  265. package/token/repositories/token-repository-documentdb.js +137 -0
  266. package/token/repositories/token-repository-factory.js +40 -0
  267. package/token/repositories/token-repository-interface.js +131 -0
  268. package/token/repositories/token-repository-mongo.js +219 -0
  269. package/token/repositories/token-repository-postgres.js +264 -0
  270. package/token/repositories/token-repository.js +219 -0
  271. package/types/associations/index.d.ts +0 -17
  272. package/types/core/index.d.ts +12 -4
  273. package/types/database/index.d.ts +10 -2
  274. package/types/encrypt/index.d.ts +5 -3
  275. package/types/integrations/index.d.ts +3 -8
  276. package/types/module-plugin/index.d.ts +17 -69
  277. package/types/syncs/index.d.ts +0 -17
  278. package/user/repositories/user-repository-documentdb.js +458 -0
  279. package/user/repositories/user-repository-factory.js +52 -0
  280. package/user/repositories/user-repository-interface.js +214 -0
  281. package/user/repositories/user-repository-mongo.js +323 -0
  282. package/user/repositories/user-repository-postgres.js +377 -0
  283. package/user/tests/doubles/test-user-repository.js +72 -0
  284. package/user/use-cases/authenticate-user.js +127 -0
  285. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  286. package/user/use-cases/create-individual-user.js +61 -0
  287. package/user/use-cases/create-organization-user.js +47 -0
  288. package/user/use-cases/create-token-for-user-id.js +30 -0
  289. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  290. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  291. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  292. package/user/use-cases/login-user.js +122 -0
  293. package/user/user.js +125 -0
  294. package/utils/backend-path.js +38 -0
  295. package/utils/index.js +6 -0
  296. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  297. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  298. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  299. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  300. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  301. package/websocket/repositories/websocket-connection-repository.js +161 -0
  302. package/assertions/is-equal.js +0 -17
  303. package/associations/model.js +0 -54
  304. package/database/models/IndividualUser.js +0 -76
  305. package/database/models/OrganizationUser.js +0 -29
  306. package/database/models/State.js +0 -9
  307. package/database/models/Token.js +0 -70
  308. package/database/models/UserModel.js +0 -7
  309. package/database/models/WebsocketConnection.js +0 -49
  310. package/database/mongo.js +0 -45
  311. package/database/mongoose.js +0 -5
  312. package/encrypt/Cryptor.test.js +0 -32
  313. package/encrypt/encrypt.js +0 -132
  314. package/encrypt/encrypt.test.js +0 -1069
  315. package/encrypt/test-encrypt.js +0 -107
  316. package/errors/base-error.test.js +0 -32
  317. package/errors/fetch-error.test.js +0 -79
  318. package/errors/halt-error.test.js +0 -11
  319. package/errors/validation-errors.test.js +0 -120
  320. package/handlers/routers/middleware/loadUser.js +0 -15
  321. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  322. package/integrations/create-frigg-backend.js +0 -31
  323. package/integrations/integration-factory.js +0 -251
  324. package/integrations/integration-mapping.js +0 -43
  325. package/integrations/integration-model.js +0 -46
  326. package/integrations/integration-user.js +0 -144
  327. package/integrations/test/integration-base.test.js +0 -144
  328. package/lambda/TimeoutCatcher.test.js +0 -68
  329. package/logs/logger.test.js +0 -76
  330. package/module-plugin/auther.js +0 -393
  331. package/module-plugin/credential.js +0 -22
  332. package/module-plugin/entity-manager.js +0 -70
  333. package/module-plugin/entity.js +0 -46
  334. package/module-plugin/manager.js +0 -169
  335. package/module-plugin/module-factory.js +0 -61
  336. package/module-plugin/requester/api-key.js +0 -36
  337. package/module-plugin/requester/oauth-2.js +0 -219
  338. package/module-plugin/requester/requester.js +0 -165
  339. package/module-plugin/requester/requester.test.js +0 -28
  340. package/module-plugin/test/auther.test.js +0 -97
  341. package/syncs/model.js +0 -62
  342. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  343. /package/{module-plugin → modules}/requester/basic.js +0 -0
  344. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,210 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ScriptScheduleRepositoryInterface,
4
+ } = require('./script-schedule-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Script Schedule Repository Adapter
8
+ * Handles script schedule persistence using Prisma with PostgreSQL
9
+ *
10
+ * PostgreSQL-specific characteristics:
11
+ * - Uses Int IDs with autoincrement
12
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
13
+ * - All returned IDs are converted to strings for application layer consistency
14
+ * - scriptName has unique index
15
+ */
16
+ class ScriptScheduleRepositoryPostgres extends ScriptScheduleRepositoryInterface {
17
+ constructor() {
18
+ super();
19
+ this.prisma = prisma;
20
+ }
21
+
22
+ /**
23
+ * Convert string ID to integer for PostgreSQL queries
24
+ * @private
25
+ * @param {string|number|null|undefined} id - ID to convert
26
+ * @returns {number|null|undefined} Integer ID or null/undefined
27
+ * @throws {Error} If ID cannot be converted to integer
28
+ */
29
+ _convertId(id) {
30
+ if (id === null || id === undefined) return id;
31
+ const parsed = Number.parseInt(id, 10);
32
+ if (Number.isNaN(parsed)) {
33
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
34
+ }
35
+ return parsed;
36
+ }
37
+
38
+ /**
39
+ * Convert schedule object IDs to strings
40
+ * @private
41
+ * @param {Object|null} schedule - Schedule object from database
42
+ * @returns {Object|null} Schedule with string IDs
43
+ */
44
+ _convertScheduleIds(schedule) {
45
+ if (!schedule) return schedule;
46
+ return {
47
+ ...schedule,
48
+ id: schedule.id?.toString(),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Find a schedule by script name
54
+ *
55
+ * @param {string} scriptName - The script name
56
+ * @returns {Promise<Object|null>} Schedule record with string ID or null if not found
57
+ */
58
+ async findScheduleByScriptName(scriptName) {
59
+ const schedule = await this.prisma.scriptSchedule.findUnique({
60
+ where: { scriptName },
61
+ });
62
+
63
+ return this._convertScheduleIds(schedule);
64
+ }
65
+
66
+ /**
67
+ * Create or update a schedule (upsert)
68
+ *
69
+ * @param {Object} params - Schedule parameters
70
+ * @param {string} params.scriptName - Name of the script
71
+ * @param {boolean} params.enabled - Whether schedule is enabled
72
+ * @param {string} params.cronExpression - Cron expression
73
+ * @param {string} [params.timezone] - Timezone (default 'UTC')
74
+ * @param {string} [params.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
75
+ * @param {string} [params.externalScheduleName] - External scheduler name
76
+ * @returns {Promise<Object>} Created or updated schedule record with string ID
77
+ */
78
+ async upsertSchedule({ scriptName, enabled, cronExpression, timezone, externalScheduleId, externalScheduleName }) {
79
+ const data = {
80
+ enabled,
81
+ cronExpression,
82
+ timezone: timezone || 'UTC',
83
+ };
84
+
85
+ // Only set external scheduler fields if provided
86
+ if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
87
+ if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
88
+
89
+ const schedule = await this.prisma.scriptSchedule.upsert({
90
+ where: { scriptName },
91
+ update: data,
92
+ create: {
93
+ scriptName,
94
+ ...data,
95
+ },
96
+ });
97
+
98
+ return this._convertScheduleIds(schedule);
99
+ }
100
+
101
+ /**
102
+ * Delete a schedule by script name
103
+ *
104
+ * @param {string} scriptName - The script name
105
+ * @returns {Promise<Object>} Deletion result
106
+ */
107
+ async deleteSchedule(scriptName) {
108
+ try {
109
+ const schedule = await this.prisma.scriptSchedule.delete({
110
+ where: { scriptName },
111
+ });
112
+
113
+ return {
114
+ acknowledged: true,
115
+ deletedCount: 1,
116
+ deleted: this._convertScheduleIds(schedule),
117
+ };
118
+ } catch (error) {
119
+ // Return 0 count if not found
120
+ if (error.code === 'P2025') {
121
+ return {
122
+ acknowledged: true,
123
+ deletedCount: 0,
124
+ };
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Update external scheduler information
132
+ *
133
+ * @param {string} scriptName - The script name
134
+ * @param {Object} externalInfo - External schedule information
135
+ * @param {string} [externalInfo.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
136
+ * @param {string} [externalInfo.externalScheduleName] - External scheduler name
137
+ * @returns {Promise<Object>} Updated schedule record with string ID
138
+ */
139
+ async updateScheduleExternalInfo(scriptName, { externalScheduleId, externalScheduleName }) {
140
+ const data = {};
141
+ if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
142
+ if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
143
+
144
+ const schedule = await this.prisma.scriptSchedule.update({
145
+ where: { scriptName },
146
+ data,
147
+ });
148
+
149
+ return this._convertScheduleIds(schedule);
150
+ }
151
+
152
+ /**
153
+ * Update last triggered timestamp
154
+ *
155
+ * @param {string} scriptName - The script name
156
+ * @param {Date} [timestamp] - Trigger timestamp (default: now)
157
+ * @returns {Promise<Object>} Updated schedule record with string ID
158
+ */
159
+ async updateScheduleLastTriggered(scriptName, timestamp) {
160
+ const schedule = await this.prisma.scriptSchedule.update({
161
+ where: { scriptName },
162
+ data: {
163
+ lastTriggeredAt: timestamp || new Date(),
164
+ },
165
+ });
166
+
167
+ return this._convertScheduleIds(schedule);
168
+ }
169
+
170
+ /**
171
+ * Update next trigger timestamp
172
+ *
173
+ * @param {string} scriptName - The script name
174
+ * @param {Date} timestamp - Next trigger timestamp
175
+ * @returns {Promise<Object>} Updated schedule record with string ID
176
+ */
177
+ async updateScheduleNextTrigger(scriptName, timestamp) {
178
+ const schedule = await this.prisma.scriptSchedule.update({
179
+ where: { scriptName },
180
+ data: {
181
+ nextTriggerAt: timestamp,
182
+ },
183
+ });
184
+
185
+ return this._convertScheduleIds(schedule);
186
+ }
187
+
188
+ /**
189
+ * List all schedules
190
+ *
191
+ * @param {Object} [options] - Query options
192
+ * @param {boolean} [options.enabledOnly] - Only return enabled schedules
193
+ * @returns {Promise<Array>} Array of schedule records with string IDs
194
+ */
195
+ async listSchedules(options = {}) {
196
+ const where = {};
197
+ if (options.enabledOnly) {
198
+ where.enabled = true;
199
+ }
200
+
201
+ const schedules = await this.prisma.scriptSchedule.findMany({
202
+ where,
203
+ orderBy: { scriptName: 'asc' },
204
+ });
205
+
206
+ return schedules.map((schedule) => this._convertScheduleIds(schedule));
207
+ }
208
+ }
209
+
210
+ module.exports = { ScriptScheduleRepositoryPostgres };
@@ -0,0 +1,451 @@
1
+ # Frigg Commands - Application Service Layer
2
+
3
+ ## Overview
4
+
5
+ Frigg Commands provide a clean, stable application service layer for all database operations in the Frigg Integration Framework. They abstract away the underlying ORM (currently Mongoose) and provide a consistent API for managing users, credentials, entities, and integrations.
6
+
7
+ ## Why Use Commands?
8
+
9
+ ### 1. **ORM Independence**
10
+
11
+ Commands isolate your integration code from the underlying database implementation. This allows Frigg to migrate between ORMs (e.g., Mongoose to Prisma) without breaking your integration code.
12
+
13
+ ### 2. **Hexagonal Architecture**
14
+
15
+ Commands act as the **application service layer** in hexagonal architecture:
16
+
17
+ - **Domain Layer**: Your use cases and business logic
18
+ - **Application Layer**: Frigg Commands (this layer)
19
+ - **Infrastructure Layer**: Repositories and database models (hidden from you)
20
+
21
+ ### 3. **Single Source of Truth**
22
+
23
+ All database operations flow through commands, making it easier to:
24
+
25
+ - Add caching, logging, or monitoring
26
+ - Enforce data validation rules
27
+ - Maintain consistent error handling
28
+ - Track data access patterns
29
+
30
+ ### 4. **Future-Proof**
31
+
32
+ When Frigg upgrades its internals, commands maintain backward compatibility. Your integration code continues working without changes.
33
+
34
+ ## Installation
35
+
36
+ Commands are available through the `@friggframework/core` package:
37
+
38
+ ```javascript
39
+ const { createFriggCommands } = require('@friggframework/core');
40
+ ```
41
+
42
+ ## Basic Usage
43
+
44
+ ### Initialize Commands
45
+
46
+ ```javascript
47
+ const { createFriggCommands } = require('@friggframework/core');
48
+ const MyIntegration = require('./MyIntegration');
49
+
50
+ // Create command set with your integration class
51
+ const commands = createFriggCommands({
52
+ integrationClass: MyIntegration,
53
+ });
54
+ ```
55
+
56
+ ### Use Commands in Your Integration
57
+
58
+ ```javascript
59
+ class MyIntegration extends IntegrationBase {
60
+ constructor() {
61
+ super();
62
+ this.commands = createFriggCommands({
63
+ integrationClass: MyIntegration,
64
+ });
65
+ }
66
+
67
+ async hydrateFromExternalUser(externalUserId) {
68
+ // Find integration context by external entity ID
69
+ const result =
70
+ await this.commands.findIntegrationContextByExternalEntityId(
71
+ externalUserId
72
+ );
73
+
74
+ if (result.error) {
75
+ return { error: result.error };
76
+ }
77
+
78
+ // Hydrate integration with retrieved context
79
+ this.setIntegrationRecord(result.context);
80
+ return { record: this.record };
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### Use Commands in Use Cases
86
+
87
+ ```javascript
88
+ const { createFriggCommands } = require('@friggframework/core');
89
+
90
+ class AuthenticateUserUseCase {
91
+ constructor({ commands } = {}) {
92
+ // Accept injected commands for testing, or create default
93
+ this.commands =
94
+ commands ||
95
+ createFriggCommands({
96
+ integrationClass: MyIntegration,
97
+ });
98
+ }
99
+
100
+ async execute({ appUserId, username, email }) {
101
+ // Find or create user
102
+ let user = await this.commands.findUserByAppUserId(appUserId);
103
+
104
+ if (!user) {
105
+ user = await this.commands.createUser({
106
+ appUserId,
107
+ username,
108
+ email,
109
+ });
110
+ }
111
+
112
+ return user;
113
+ }
114
+ }
115
+ ```
116
+
117
+ ## Available Commands
118
+
119
+ ### User Commands
120
+
121
+ Manage Frigg users (individuals or organizations using your integration).
122
+
123
+ ```javascript
124
+ // Create a new user
125
+ const user = await commands.createUser({
126
+ username: 'john@example.com',
127
+ email: 'john@example.com',
128
+ appUserId: 'external-user-123',
129
+ password: 'optional-password', // For password-based auth
130
+ });
131
+
132
+ // Find user by app-specific user ID
133
+ const user = await commands.findUserByAppUserId('external-user-123');
134
+
135
+ // Find user by username
136
+ const user = await commands.findUserByUsername('john@example.com');
137
+
138
+ // Find user by Frigg internal ID
139
+ const user = await commands.findIndividualUserById('frigg-user-id');
140
+
141
+ // Update user
142
+ const updatedUser = await commands.updateUser('frigg-user-id', {
143
+ email: 'newemail@example.com',
144
+ });
145
+ ```
146
+
147
+ ### Credential Commands
148
+
149
+ Manage OAuth tokens and API credentials.
150
+
151
+ ```javascript
152
+ // Create credential
153
+ const credential = await commands.createCredential({
154
+ userId: 'frigg-user-id',
155
+ externalId: 'oauth-user-id',
156
+ access_token: 'access_token_value',
157
+ refresh_token: 'refresh_token_value',
158
+ expires_at: new Date('2024-12-31'),
159
+ moduleName: 'asana',
160
+ authIsValid: true,
161
+ });
162
+
163
+ // Find credential
164
+ const credential = await commands.findCredential({
165
+ userId: 'frigg-user-id',
166
+ moduleName: 'asana',
167
+ });
168
+
169
+ // Update credential (e.g., after token refresh)
170
+ const updated = await commands.updateCredential('credential-id', {
171
+ access_token: 'new_access_token',
172
+ expires_at: new Date('2025-01-31'),
173
+ });
174
+
175
+ // Delete credential
176
+ await commands.deleteCredential('credential-id');
177
+ ```
178
+
179
+ ### Entity Commands
180
+
181
+ Manage module entities (connections to external services).
182
+
183
+ ```javascript
184
+ // Create entity
185
+ const entity = await commands.createEntity({
186
+ userId: 'frigg-user-id',
187
+ externalId: 'asana-workspace-123',
188
+ name: 'My Workspace',
189
+ moduleName: 'asana',
190
+ credentialId: 'credential-id',
191
+ });
192
+
193
+ // Find single entity
194
+ const entity = await commands.findEntity({
195
+ userId: 'frigg-user-id',
196
+ externalId: 'asana-workspace-123',
197
+ moduleName: 'asana',
198
+ });
199
+
200
+ // Find entity by ID
201
+ const entity = await commands.findEntityById('entity-id');
202
+
203
+ // Find all entities for user
204
+ const entities = await commands.findEntitiesByUserId('frigg-user-id');
205
+
206
+ // Find entities by module
207
+ const asanaEntities = await commands.findEntitiesByUserIdAndModuleName(
208
+ 'frigg-user-id',
209
+ 'asana'
210
+ );
211
+
212
+ // Find multiple entities by IDs
213
+ const entities = await commands.findEntitiesByIds([
214
+ 'entity-id-1',
215
+ 'entity-id-2',
216
+ ]);
217
+
218
+ // Update entity
219
+ const updated = await commands.updateEntity('entity-id', {
220
+ name: 'Updated Workspace Name',
221
+ });
222
+
223
+ // Delete entity
224
+ await commands.deleteEntity('entity-id');
225
+ ```
226
+
227
+ ### Integration Commands
228
+
229
+ Manage integration records and load full integration contexts.
230
+
231
+ ```javascript
232
+ // Find integration context by external entity ID
233
+ // Returns { context, error } where context includes record + hydrated modules
234
+ const result = await commands.findIntegrationContextByExternalEntityId(
235
+ 'external-user-or-workspace-id'
236
+ );
237
+
238
+ if (!result.error) {
239
+ integration.setIntegrationRecord(result.context);
240
+ }
241
+
242
+ // Load integration context by integration ID
243
+ const result = await commands.loadIntegrationContextById('integration-id');
244
+
245
+ if (!result.error) {
246
+ integration.setIntegrationRecord(result.context);
247
+ }
248
+ ```
249
+
250
+ ## Architecture Principles
251
+
252
+ ### Dependency Injection for Testing
253
+
254
+ Commands support dependency injection for testing:
255
+
256
+ ```javascript
257
+ // Production code - uses real repositories
258
+ const commands = createFriggCommands({ integrationClass: MyIntegration });
259
+
260
+ // Test code - inject mocks
261
+ const mockCommands = {
262
+ createUser: jest.fn().mockResolvedValue({ id: 'user-123' }),
263
+ findUserByAppUserId: jest.fn().mockResolvedValue(null),
264
+ };
265
+
266
+ const useCase = new MyUseCase({ commands: mockCommands });
267
+ ```
268
+
269
+ ### Integration vs Unit Testing
270
+
271
+ **Commands are designed for integration testing** - they use real repositories by default:
272
+
273
+ ```javascript
274
+ // ❌ Don't do this - commands always use real repositories
275
+ const commands = createFriggCommands({
276
+ userRepository: mockUserRepo, // This parameter doesn't exist
277
+ });
278
+
279
+ // ✅ Do this - inject mocked commands into your use cases
280
+ const useCase = new MyUseCase({
281
+ commands: mockCommands,
282
+ });
283
+ ```
284
+
285
+ ### Error Handling
286
+
287
+ Commands return domain objects directly. Handle errors at the use case level:
288
+
289
+ ```javascript
290
+ try {
291
+ const user = await commands.createUser({ username, email });
292
+ return { success: true, user };
293
+ } catch (error) {
294
+ // Handle database errors
295
+ return { success: false, error: error.message };
296
+ }
297
+ ```
298
+
299
+ For integration context operations, errors are returned in the result:
300
+
301
+ ```javascript
302
+ const result = await commands.findIntegrationContextByExternalEntityId(userId);
303
+
304
+ if (result.error) {
305
+ return { error: result.error };
306
+ }
307
+
308
+ // Use result.context
309
+ ```
310
+
311
+ ## Migration Guide
312
+
313
+ ### From Direct Model Access
314
+
315
+ **Before (❌ Don't do this):**
316
+
317
+ ```javascript
318
+ const { User } = require('@friggframework/core');
319
+
320
+ const user = await User.findOne({ appUserId: '123' });
321
+ ```
322
+
323
+ **After (✅ Do this):**
324
+
325
+ ```javascript
326
+ const { createFriggCommands } = require('@friggframework/core');
327
+
328
+ const commands = createFriggCommands({ integrationClass: MyIntegration });
329
+ const user = await commands.findUserByAppUserId('123');
330
+ ```
331
+
332
+ ### From IntegrationRepository (Backend Pattern)
333
+
334
+ **Before (❌ Old pattern):**
335
+
336
+ ```javascript
337
+ const {
338
+ IntegrationRepository,
339
+ } = require('./repositories/IntegrationRepository');
340
+
341
+ this.integrationRepository = new IntegrationRepository(MyIntegration);
342
+ const result =
343
+ await this.integrationRepository.loadIntegrationRecordByAsanaUser(userId);
344
+ ```
345
+
346
+ **After (✅ New pattern):**
347
+
348
+ ```javascript
349
+ const { createFriggCommands } = require('@friggframework/core');
350
+
351
+ this.commands = createFriggCommands({ integrationClass: MyIntegration });
352
+ const result = await this.commands.findIntegrationContextByExternalEntityId(
353
+ userId
354
+ );
355
+ ```
356
+
357
+ ## Best Practices
358
+
359
+ ### 1. Create Commands Once
360
+
361
+ Initialize commands in your constructor:
362
+
363
+ ```javascript
364
+ class MyIntegration extends IntegrationBase {
365
+ constructor() {
366
+ super();
367
+ this.commands = createFriggCommands({
368
+ integrationClass: MyIntegration,
369
+ });
370
+ }
371
+ }
372
+ ```
373
+
374
+ ### 2. Pass Commands to Use Cases
375
+
376
+ Use dependency injection for testability:
377
+
378
+ ```javascript
379
+ class MyUseCase {
380
+ constructor({ commands } = {}) {
381
+ this.commands =
382
+ commands ||
383
+ createFriggCommands({
384
+ integrationClass: MyIntegration,
385
+ });
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### 3. Use Specific Finders
391
+
392
+ Use the most specific finder method:
393
+
394
+ ```javascript
395
+ // ✅ Good - specific finder
396
+ const user = await commands.findUserByAppUserId('123');
397
+
398
+ // ❌ Less efficient - generic finder
399
+ const user = await commands.findUser({ appUserId: '123' });
400
+ ```
401
+
402
+ ### 4. Handle Null Returns
403
+
404
+ Most finders return `null` if not found:
405
+
406
+ ```javascript
407
+ const user = await commands.findUserByAppUserId('123');
408
+
409
+ if (!user) {
410
+ // Handle user not found
411
+ user = await commands.createUser({ ... });
412
+ }
413
+ ```
414
+
415
+ ## Command Reference
416
+
417
+ | Category | Command | Description |
418
+ | --------------- | ------------------------------------------------------- | ----------------------------------------- |
419
+ | **User** | `createUser(data)` | Create new Frigg user |
420
+ | | `findUserByAppUserId(appUserId)` | Find by external app user ID |
421
+ | | `findUserByUsername(username)` | Find by username |
422
+ | | `findIndividualUserById(id)` | Find by Frigg user ID |
423
+ | | `updateUser(id, updates)` | Update user properties |
424
+ | **Credential** | `createCredential(data)` | Create OAuth credential |
425
+ | | `findCredential(filter)` | Find credential by filter |
426
+ | | `updateCredential(id, updates)` | Update credential (token refresh) |
427
+ | | `deleteCredential(id)` | Delete credential |
428
+ | **Entity** | `createEntity(data)` | Create module entity |
429
+ | | `findEntity(filter)` | Find entity by filter |
430
+ | | `findEntityById(id)` | Find by entity ID |
431
+ | | `findEntitiesByUserId(userId)` | Find all user entities |
432
+ | | `findEntitiesByUserIdAndModuleName(userId, moduleName)` | Find user entities for module |
433
+ | | `findEntitiesByIds(ids)` | Find multiple by IDs |
434
+ | | `updateEntity(id, updates)` | Update entity properties |
435
+ | | `deleteEntity(id)` | Delete entity |
436
+ | **Integration** | `findIntegrationContextByExternalEntityId(externalId)` | Load integration + modules by external ID |
437
+ | | `loadIntegrationContextById(integrationId)` | Load integration + modules by ID |
438
+
439
+ ## Support
440
+
441
+ For questions or issues with commands:
442
+
443
+ 1. Check this README
444
+ 2. Review the main Frigg documentation
445
+ 3. Open an issue on the Frigg Framework repository
446
+
447
+ ## Related Documentation
448
+
449
+ - [Frigg Framework Overview](../../README.md)
450
+ - [Integration Development Guide](../../docs/integration-guide.md)
451
+ - [Hexagonal Architecture](../../docs/architecture.md)