@friggframework/core 2.0.0-next.12 → 2.0.0-next.120

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 (410) hide show
  1. package/.eslintrc.json +18 -1
  2. package/CLAUDE.md +830 -0
  3. package/README.md +996 -50
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
  9. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
  10. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  12. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  13. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  14. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  15. package/application/commands/README.md +451 -0
  16. package/application/commands/admin-script-commands.js +359 -0
  17. package/application/commands/credential-commands.js +262 -0
  18. package/application/commands/entity-commands.js +294 -0
  19. package/application/commands/integration-commands.js +419 -0
  20. package/application/commands/integration-mapping-commands.js +25 -0
  21. package/application/commands/process-commands.js +135 -0
  22. package/application/commands/report-commands.js +188 -0
  23. package/application/commands/scheduler-commands.js +263 -0
  24. package/application/commands/usage-commands.js +56 -0
  25. package/application/commands/user-commands.js +312 -0
  26. package/application/index.js +83 -0
  27. package/artifacts/repositories/artifact-repository-factory.js +19 -0
  28. package/artifacts/repositories/artifact-repository-interface.js +27 -0
  29. package/artifacts/repositories/artifact-repository-local.js +42 -0
  30. package/artifacts/repositories/artifact-repository-s3.js +61 -0
  31. package/assertions/index.js +0 -3
  32. package/core/CLAUDE.md +715 -0
  33. package/core/Worker.js +55 -24
  34. package/core/create-handler.js +121 -38
  35. package/core/index.js +8 -1
  36. package/core/invocation-scope.js +225 -0
  37. package/core/parameters-to-env.js +257 -0
  38. package/core/secrets-to-env.js +7 -6
  39. package/core/ssm-preload.mjs +34 -0
  40. package/credential/repositories/credential-active-type.js +32 -0
  41. package/credential/repositories/credential-repository-documentdb.js +355 -0
  42. package/credential/repositories/credential-repository-factory.js +54 -0
  43. package/credential/repositories/credential-repository-interface.js +113 -0
  44. package/credential/repositories/credential-repository-mongo.js +294 -0
  45. package/credential/repositories/credential-repository-postgres.js +312 -0
  46. package/credential/repositories/credential-repository.js +300 -0
  47. package/credential/use-cases/get-credential-for-user.js +25 -0
  48. package/credential/use-cases/update-authentication-status.js +15 -0
  49. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  50. package/database/adapters/lambda-invoker.js +97 -0
  51. package/database/config.js +147 -0
  52. package/database/documentdb-encryption-service.js +334 -0
  53. package/database/documentdb-utils.js +196 -0
  54. package/database/encryption/README.md +863 -0
  55. package/database/encryption/documentdb-encryption-service.md +3575 -0
  56. package/database/encryption/encryption-schema-registry.js +404 -0
  57. package/database/encryption/field-encryption-service.js +254 -0
  58. package/database/encryption/integration-mapping-encryption.js +97 -0
  59. package/database/encryption/logger.js +82 -0
  60. package/database/encryption/prisma-encryption-extension.js +241 -0
  61. package/database/index.js +21 -21
  62. package/database/prisma.js +182 -0
  63. package/database/repositories/health-check-repository-documentdb.js +138 -0
  64. package/database/repositories/health-check-repository-factory.js +48 -0
  65. package/database/repositories/health-check-repository-interface.js +82 -0
  66. package/database/repositories/health-check-repository-mongodb.js +89 -0
  67. package/database/repositories/health-check-repository-postgres.js +82 -0
  68. package/database/repositories/migration-status-repository-s3.js +137 -0
  69. package/database/use-cases/check-database-health-use-case.js +29 -0
  70. package/database/use-cases/check-database-state-use-case.js +81 -0
  71. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  72. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  73. package/database/use-cases/get-migration-status-use-case.js +93 -0
  74. package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
  75. package/database/use-cases/run-database-migration-use-case.js +139 -0
  76. package/database/use-cases/test-encryption-use-case.js +253 -0
  77. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  78. package/database/utils/mongodb-collection-utils.js +94 -0
  79. package/database/utils/mongodb-schema-init.js +108 -0
  80. package/database/utils/prisma-runner.js +491 -0
  81. package/database/utils/prisma-schema-parser.js +182 -0
  82. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  83. package/encrypt/Cryptor.js +34 -168
  84. package/encrypt/index.js +1 -2
  85. package/errors/client-safe-error.js +26 -0
  86. package/errors/fetch-error.js +59 -57
  87. package/errors/index.js +6 -0
  88. package/errors/mapping-already-exists-error.js +14 -0
  89. package/generated/prisma-mongodb/client.d.ts +1 -0
  90. package/generated/prisma-mongodb/client.js +5 -0
  91. package/generated/prisma-mongodb/default.d.ts +1 -0
  92. package/generated/prisma-mongodb/default.js +5 -0
  93. package/generated/prisma-mongodb/edge.d.ts +1 -0
  94. package/generated/prisma-mongodb/edge.js +386 -0
  95. package/generated/prisma-mongodb/index-browser.js +368 -0
  96. package/generated/prisma-mongodb/index.d.ts +27065 -0
  97. package/generated/prisma-mongodb/index.js +411 -0
  98. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  99. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  100. package/generated/prisma-mongodb/package.json +183 -0
  101. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  102. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  103. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  104. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  105. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  106. package/generated/prisma-mongodb/runtime/library.js +147 -0
  107. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  108. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  109. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  110. package/generated/prisma-mongodb/schema.prisma +455 -0
  111. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  112. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  113. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  114. package/generated/prisma-mongodb/wasm.js +393 -0
  115. package/generated/prisma-postgresql/client.d.ts +1 -0
  116. package/generated/prisma-postgresql/client.js +5 -0
  117. package/generated/prisma-postgresql/default.d.ts +1 -0
  118. package/generated/prisma-postgresql/default.js +5 -0
  119. package/generated/prisma-postgresql/edge.d.ts +1 -0
  120. package/generated/prisma-postgresql/edge.js +407 -0
  121. package/generated/prisma-postgresql/index-browser.js +389 -0
  122. package/generated/prisma-postgresql/index.d.ts +29904 -0
  123. package/generated/prisma-postgresql/index.js +432 -0
  124. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  125. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  126. package/generated/prisma-postgresql/package.json +183 -0
  127. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  128. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  129. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  130. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  131. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  132. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  133. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  134. package/generated/prisma-postgresql/runtime/library.js +147 -0
  135. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  136. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  137. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  138. package/generated/prisma-postgresql/schema.prisma +436 -0
  139. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  140. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  141. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  142. package/generated/prisma-postgresql/wasm.js +414 -0
  143. package/handlers/WEBHOOKS.md +682 -0
  144. package/handlers/app-definition-loader.js +103 -0
  145. package/handlers/app-handler-helpers.js +20 -6
  146. package/handlers/backend-utils.js +272 -46
  147. package/handlers/database-migration-handler.js +236 -0
  148. package/handlers/integration-event-dispatcher.js +68 -0
  149. package/handlers/middleware/admin-auth.js +73 -0
  150. package/handlers/routers/HEALTHCHECK.md +342 -0
  151. package/handlers/routers/auth.js +4 -15
  152. package/handlers/routers/db-migration.handler.js +29 -0
  153. package/handlers/routers/db-migration.js +329 -0
  154. package/handlers/routers/health.js +518 -0
  155. package/handlers/routers/integration-defined-routers.js +85 -10
  156. package/handlers/routers/integration-webhook-routers.js +87 -0
  157. package/handlers/routers/user.js +27 -5
  158. package/handlers/routers/websocket.js +31 -9
  159. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  160. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  161. package/handlers/workers/db-migration.js +438 -0
  162. package/handlers/workers/dlq-processor.js +89 -0
  163. package/handlers/workers/integration-defined-workers.js +13 -7
  164. package/index.js +123 -47
  165. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  166. package/infrastructure/scheduler/index.js +33 -0
  167. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  168. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  169. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  170. package/integrations/EXTENSIONS.md +240 -0
  171. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  172. package/integrations/extension.js +254 -0
  173. package/integrations/index.js +20 -10
  174. package/integrations/integration-base.js +777 -74
  175. package/integrations/integration-router.js +399 -180
  176. package/integrations/options.js +1 -1
  177. package/integrations/repositories/config-patch-shared.js +43 -0
  178. package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
  179. package/integrations/repositories/integration-mapping-query.js +193 -0
  180. package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
  181. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  182. package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
  183. package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
  184. package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
  185. package/integrations/repositories/integration-mapping-repository.js +185 -0
  186. package/integrations/repositories/integration-repository-documentdb.js +354 -0
  187. package/integrations/repositories/integration-repository-factory.js +51 -0
  188. package/integrations/repositories/integration-repository-interface.js +219 -0
  189. package/integrations/repositories/integration-repository-mongo.js +502 -0
  190. package/integrations/repositories/integration-repository-postgres.js +550 -0
  191. package/integrations/repositories/process-repository-documentdb.js +311 -0
  192. package/integrations/repositories/process-repository-factory.js +53 -0
  193. package/integrations/repositories/process-repository-interface.js +136 -0
  194. package/integrations/repositories/process-repository-mongo.js +262 -0
  195. package/integrations/repositories/process-repository-postgres.js +380 -0
  196. package/integrations/repositories/process-update-ops-shared.js +112 -0
  197. package/integrations/repositories/report-id.js +13 -0
  198. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  199. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  200. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  201. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  202. package/integrations/use-cases/create-integration.js +253 -0
  203. package/integrations/use-cases/create-process.js +130 -0
  204. package/integrations/use-cases/delete-integration-for-user.js +130 -0
  205. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  206. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  207. package/integrations/use-cases/get-integration-for-user.js +78 -0
  208. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  209. package/integrations/use-cases/get-integration-instance.js +83 -0
  210. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  211. package/integrations/use-cases/get-possible-integrations.js +27 -0
  212. package/integrations/use-cases/get-process.js +89 -0
  213. package/integrations/use-cases/index.js +19 -0
  214. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  215. package/integrations/use-cases/load-integration-context.js +71 -0
  216. package/integrations/use-cases/patch-integration-config.js +39 -0
  217. package/integrations/use-cases/process-errors.js +28 -0
  218. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  219. package/integrations/use-cases/update-integration-config.js +32 -0
  220. package/integrations/use-cases/update-integration-messages.js +44 -0
  221. package/integrations/use-cases/update-integration-status.js +32 -0
  222. package/integrations/use-cases/update-integration.js +92 -0
  223. package/integrations/use-cases/update-process-metrics.js +222 -0
  224. package/integrations/use-cases/update-process-state.js +163 -0
  225. package/integrations/utils/assert-entity-ids-input.js +42 -0
  226. package/integrations/utils/map-integration-dto.js +37 -0
  227. package/jest-global-setup-noop.js +3 -0
  228. package/jest-global-teardown-noop.js +3 -0
  229. package/jest.config.js +2 -0
  230. package/logs/.eslintrc.json +26 -1
  231. package/logs/__fixtures__/events.js +82 -0
  232. package/logs/__fixtures__/matchers.js +45 -0
  233. package/logs/__fixtures__/secrets.js +59 -0
  234. package/logs/__fixtures__/vectors.js +329 -0
  235. package/logs/__fixtures__/with-env.js +45 -0
  236. package/logs/context.js +109 -0
  237. package/logs/contract-keys.js +67 -0
  238. package/logs/debug-shims.js +46 -0
  239. package/logs/denied-keys.js +21 -0
  240. package/logs/index.js +14 -3
  241. package/logs/jest-logger-setup.js +25 -0
  242. package/logs/levels.js +71 -0
  243. package/logs/logger-runtime.js +137 -0
  244. package/logs/logger.js +134 -46
  245. package/logs/record.js +276 -0
  246. package/logs/redact.js +328 -0
  247. package/logs/serialize.js +329 -0
  248. package/logs/sinks.js +93 -0
  249. package/logs/summarize-event.js +163 -0
  250. package/{module-plugin → modules}/index.js +0 -10
  251. package/modules/module-factory.js +56 -0
  252. package/modules/module.js +320 -0
  253. package/modules/repositories/module-repository-documentdb.js +350 -0
  254. package/modules/repositories/module-repository-factory.js +40 -0
  255. package/modules/repositories/module-repository-interface.js +145 -0
  256. package/modules/repositories/module-repository-mongo.js +436 -0
  257. package/modules/repositories/module-repository-postgres.js +481 -0
  258. package/modules/repositories/module-repository.js +369 -0
  259. package/modules/requester/api-key.js +52 -0
  260. package/modules/requester/oauth-2.js +561 -0
  261. package/modules/requester/requester.js +605 -0
  262. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  263. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  264. package/modules/tests/doubles/test-module-factory.js +16 -0
  265. package/modules/tests/doubles/test-module-repository.js +39 -0
  266. package/modules/use-cases/get-entities-for-user.js +32 -0
  267. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  268. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  269. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  270. package/modules/use-cases/get-module.js +74 -0
  271. package/modules/use-cases/process-authorization-callback.js +294 -0
  272. package/modules/use-cases/refresh-entity-options.js +72 -0
  273. package/modules/use-cases/test-module-auth.js +72 -0
  274. package/modules/utils/map-module-dto.js +20 -0
  275. package/modules/utils/to-credential-id.js +14 -0
  276. package/package.json +91 -52
  277. package/prisma-mongodb/schema.prisma +455 -0
  278. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  279. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  280. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  281. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  282. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  283. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  284. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  285. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  286. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  287. package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
  288. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  289. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  290. package/prisma-postgresql/schema.prisma +436 -0
  291. package/queues/queue-delivery.js +41 -0
  292. package/queues/queuer-util.js +89 -21
  293. package/reporting/README.md +154 -0
  294. package/reporting/builtin-reports.js +6 -0
  295. package/reporting/index.js +13 -0
  296. package/reporting/report-base.js +49 -0
  297. package/reporting/reports/integrations-report.js +221 -0
  298. package/syncs/manager.js +468 -443
  299. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  300. package/syncs/repositories/sync-repository-factory.js +43 -0
  301. package/syncs/repositories/sync-repository-interface.js +109 -0
  302. package/syncs/repositories/sync-repository-mongo.js +239 -0
  303. package/syncs/repositories/sync-repository-postgres.js +319 -0
  304. package/syncs/sync.js +0 -1
  305. package/telemetry/README.md +331 -0
  306. package/telemetry/bind-telemetry-context.js +77 -0
  307. package/telemetry/canonical-counters.js +52 -0
  308. package/telemetry/exporters.js +85 -0
  309. package/telemetry/index.js +26 -0
  310. package/telemetry/instrument-handler.js +104 -0
  311. package/telemetry/no-op-telemetry.js +71 -0
  312. package/telemetry/north-star.js +103 -0
  313. package/telemetry/otel-telemetry.js +232 -0
  314. package/telemetry/plugin-subscribers.js +77 -0
  315. package/telemetry/telemetry-config.js +120 -0
  316. package/telemetry/telemetry-context.js +51 -0
  317. package/telemetry/telemetry-event-bus.js +58 -0
  318. package/telemetry/telemetry-runtime.js +164 -0
  319. package/telemetry/telemetry-service.js +52 -0
  320. package/telemetry/usage-rollup-subscriber.js +116 -0
  321. package/token/repositories/token-repository-documentdb.js +137 -0
  322. package/token/repositories/token-repository-factory.js +40 -0
  323. package/token/repositories/token-repository-interface.js +131 -0
  324. package/token/repositories/token-repository-mongo.js +219 -0
  325. package/token/repositories/token-repository-postgres.js +264 -0
  326. package/token/repositories/token-repository.js +219 -0
  327. package/types/associations/index.d.ts +0 -17
  328. package/types/core/index.d.ts +12 -4
  329. package/types/database/index.d.ts +10 -2
  330. package/types/encrypt/index.d.ts +5 -3
  331. package/types/errors/index.d.ts +24 -10
  332. package/types/integrations/index.d.ts +3 -8
  333. package/types/logs/index.d.ts +61 -2
  334. package/types/module-plugin/index.d.ts +20 -69
  335. package/types/syncs/index.d.ts +0 -17
  336. package/usage/README.md +54 -0
  337. package/usage/index.js +17 -0
  338. package/usage/repositories/usage-repository-documentdb.js +194 -0
  339. package/usage/repositories/usage-repository-factory.js +25 -0
  340. package/usage/repositories/usage-repository-interface.js +37 -0
  341. package/usage/repositories/usage-repository-prisma.js +146 -0
  342. package/usage/tracked-metrics.js +38 -0
  343. package/usage/usage-windows.js +24 -0
  344. package/user/repositories/user-repository-documentdb.js +426 -0
  345. package/user/repositories/user-repository-factory.js +52 -0
  346. package/user/repositories/user-repository-interface.js +214 -0
  347. package/user/repositories/user-repository-mongo.js +323 -0
  348. package/user/repositories/user-repository-postgres.js +377 -0
  349. package/user/tests/doubles/test-user-repository.js +72 -0
  350. package/user/use-cases/authenticate-user.js +143 -0
  351. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  352. package/user/use-cases/create-individual-user.js +61 -0
  353. package/user/use-cases/create-organization-user.js +47 -0
  354. package/user/use-cases/create-token-for-user-id.js +30 -0
  355. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  356. package/user/use-cases/get-user-from-bearer-token.js +82 -0
  357. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  358. package/user/use-cases/login-user.js +122 -0
  359. package/user/user.js +125 -0
  360. package/utils/backend-path.js +38 -0
  361. package/utils/index.js +6 -0
  362. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  363. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  364. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  365. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  366. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  367. package/websocket/repositories/websocket-connection-repository.js +161 -0
  368. package/assertions/is-equal.js +0 -17
  369. package/associations/model.js +0 -54
  370. package/database/models/IndividualUser.js +0 -76
  371. package/database/models/OrganizationUser.js +0 -29
  372. package/database/models/State.js +0 -9
  373. package/database/models/Token.js +0 -70
  374. package/database/models/UserModel.js +0 -7
  375. package/database/models/WebsocketConnection.js +0 -49
  376. package/database/mongo.js +0 -45
  377. package/database/mongoose.js +0 -5
  378. package/encrypt/Cryptor.test.js +0 -32
  379. package/encrypt/encrypt.js +0 -132
  380. package/encrypt/encrypt.test.js +0 -1069
  381. package/encrypt/test-encrypt.js +0 -107
  382. package/errors/base-error.test.js +0 -32
  383. package/errors/fetch-error.test.js +0 -79
  384. package/errors/halt-error.test.js +0 -11
  385. package/errors/validation-errors.test.js +0 -120
  386. package/handlers/routers/middleware/loadUser.js +0 -15
  387. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  388. package/integrations/create-frigg-backend.js +0 -31
  389. package/integrations/integration-factory.js +0 -251
  390. package/integrations/integration-mapping.js +0 -43
  391. package/integrations/integration-model.js +0 -46
  392. package/integrations/integration-user.js +0 -144
  393. package/integrations/test/integration-base.test.js +0 -144
  394. package/lambda/TimeoutCatcher.test.js +0 -68
  395. package/logs/logger.test.js +0 -76
  396. package/module-plugin/auther.js +0 -393
  397. package/module-plugin/credential.js +0 -22
  398. package/module-plugin/entity-manager.js +0 -70
  399. package/module-plugin/entity.js +0 -46
  400. package/module-plugin/manager.js +0 -169
  401. package/module-plugin/module-factory.js +0 -61
  402. package/module-plugin/requester/api-key.js +0 -36
  403. package/module-plugin/requester/oauth-2.js +0 -219
  404. package/module-plugin/requester/requester.js +0 -165
  405. package/module-plugin/requester/requester.test.js +0 -28
  406. package/module-plugin/test/auther.test.js +0 -97
  407. package/syncs/model.js +0 -62
  408. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  409. /package/{module-plugin → modules}/requester/basic.js +0 -0
  410. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,426 @@
1
+ const bcrypt = require('bcryptjs');
2
+ const { prisma } = require('../../database/prisma');
3
+ const {
4
+ toObjectId,
5
+ fromObjectId,
6
+ findOne,
7
+ findMany,
8
+ insertOne,
9
+ updateOne,
10
+ deleteOne,
11
+ } = require('../../database/documentdb-utils');
12
+ const {
13
+ createTokenRepository,
14
+ } = require('../../token/repositories/token-repository-factory');
15
+ const { UserRepositoryInterface } = require('./user-repository-interface');
16
+ const { ClientSafeError } = require('../../errors');
17
+ const {
18
+ DocumentDBEncryptionService,
19
+ } = require('../../database/documentdb-encryption-service');
20
+ const { getLogger } = require('../../logs');
21
+
22
+ const log = getLogger('frigg.user');
23
+
24
+ /**
25
+ * User repository for DocumentDB.
26
+ * Uses DocumentDBEncryptionService for field-level encryption.
27
+ *
28
+ * Encrypted fields: User.hashword
29
+ *
30
+ * @see DocumentDBEncryptionService
31
+ * @see encryption-schema-registry.js
32
+ */
33
+ class UserRepositoryDocumentDB extends UserRepositoryInterface {
34
+ constructor() {
35
+ super();
36
+ this.prisma = prisma;
37
+ this.tokenRepository = createTokenRepository();
38
+ this.encryptionService = new DocumentDBEncryptionService();
39
+ }
40
+
41
+ async getSessionToken(token) {
42
+ const jsonToken =
43
+ this.tokenRepository.getJSONTokenFromBase64BufferToken(token);
44
+ const sessionToken = await this.tokenRepository.validateAndGetToken(
45
+ jsonToken
46
+ );
47
+ return sessionToken;
48
+ }
49
+
50
+ async findOrganizationUserById(userId) {
51
+ const doc = await findOne(this.prisma, 'User', {
52
+ _id: toObjectId(userId),
53
+ type: 'ORGANIZATION',
54
+ });
55
+ const decrypted = await this.encryptionService.decryptFields(
56
+ 'User',
57
+ doc
58
+ );
59
+ return this._mapUser(decrypted);
60
+ }
61
+
62
+ async findIndividualUserById(userId) {
63
+ const doc = await findOne(this.prisma, 'User', {
64
+ _id: toObjectId(userId),
65
+ type: 'INDIVIDUAL',
66
+ });
67
+ const decrypted = await this.encryptionService.decryptFields(
68
+ 'User',
69
+ doc
70
+ );
71
+ return this._mapUser(decrypted);
72
+ }
73
+
74
+ async findIndividualUsersByOrganizationId(organizationUserId) {
75
+ const docs = await findMany(this.prisma, 'User', {
76
+ organizationId: toObjectId(organizationUserId),
77
+ type: 'INDIVIDUAL',
78
+ });
79
+ return Promise.all(
80
+ docs.map(async (doc) => {
81
+ const decrypted = await this.encryptionService.decryptFields(
82
+ 'User',
83
+ doc
84
+ );
85
+ return this._mapUser(decrypted);
86
+ })
87
+ );
88
+ }
89
+
90
+ async createToken(userId, rawToken, minutes = 120) {
91
+ const createdToken = await this.tokenRepository.createTokenWithExpire(
92
+ fromObjectId(toObjectId(userId)),
93
+ rawToken,
94
+ minutes
95
+ );
96
+ return this.tokenRepository.createBase64BufferToken(
97
+ createdToken,
98
+ rawToken
99
+ );
100
+ }
101
+
102
+ async createIndividualUser(params) {
103
+ const now = new Date();
104
+ const document = {
105
+ type: 'INDIVIDUAL',
106
+ email: params.email ?? null,
107
+ username: params.username ?? null,
108
+ appUserId: params.appUserId ?? null,
109
+ organizationId: params.organization
110
+ ? toObjectId(params.organization)
111
+ : params.organizationId
112
+ ? toObjectId(params.organizationId)
113
+ : null,
114
+ createdAt: now,
115
+ updatedAt: now,
116
+ };
117
+
118
+ if (
119
+ params.hashword !== undefined &&
120
+ params.hashword !== null &&
121
+ params.hashword !== ''
122
+ ) {
123
+ if (typeof params.hashword !== 'string') {
124
+ throw new ClientSafeError('Password must be a string', 400);
125
+ }
126
+
127
+ if (params.hashword.startsWith('$2')) {
128
+ throw new Error(
129
+ 'Password appears to be already hashed. Pass plain text password only.'
130
+ );
131
+ }
132
+
133
+ // Bcrypt hash the password
134
+ document.hashword = await bcrypt.hash(params.hashword, 10);
135
+ }
136
+
137
+ // Encrypt sensitive fields before insert
138
+ const encryptedDocument = await this.encryptionService.encryptFields(
139
+ 'User',
140
+ document
141
+ );
142
+ const insertedId = await insertOne(
143
+ this.prisma,
144
+ 'User',
145
+ encryptedDocument
146
+ );
147
+ const created = await findOne(this.prisma, 'User', { _id: insertedId });
148
+
149
+ // Defensive check: verify document was found after insert
150
+ if (!created) {
151
+ throw new Error(
152
+ `Failed to create individual user: Document not found after insert (insertedId ${fromObjectId(insertedId)}). ` +
153
+ 'This indicates a database consistency issue.'
154
+ );
155
+ }
156
+
157
+ // Decrypt sensitive fields after read
158
+ const decrypted = await this.encryptionService.decryptFields(
159
+ 'User',
160
+ created
161
+ );
162
+
163
+ return this._mapUser(decrypted);
164
+ }
165
+
166
+ async createOrganizationUser(params) {
167
+ const now = new Date();
168
+ const document = {
169
+ type: 'ORGANIZATION',
170
+ appOrgId: params.appOrgId ?? null,
171
+ name: params.name ?? null,
172
+ createdAt: now,
173
+ updatedAt: now,
174
+ };
175
+
176
+ // Encrypt sensitive fields before insert (consistency with individual user)
177
+ const encryptedDocument = await this.encryptionService.encryptFields(
178
+ 'User',
179
+ document
180
+ );
181
+ const insertedId = await insertOne(
182
+ this.prisma,
183
+ 'User',
184
+ encryptedDocument
185
+ );
186
+ const created = await findOne(this.prisma, 'User', { _id: insertedId });
187
+
188
+ // Defensive check: verify document was found after insert
189
+ if (!created) {
190
+ throw new Error(
191
+ `Failed to create organization user: Document not found after insert (insertedId ${fromObjectId(insertedId)}). ` +
192
+ 'This indicates a database consistency issue.'
193
+ );
194
+ }
195
+
196
+ // Decrypt sensitive fields after read
197
+ const decrypted = await this.encryptionService.decryptFields(
198
+ 'User',
199
+ created
200
+ );
201
+ return this._mapUser(decrypted);
202
+ }
203
+
204
+ async findIndividualUserByUsername(username) {
205
+ const doc = await findOne(this.prisma, 'User', {
206
+ type: 'INDIVIDUAL',
207
+ username,
208
+ });
209
+ const decrypted = await this.encryptionService.decryptFields(
210
+ 'User',
211
+ doc
212
+ );
213
+ return this._mapUser(decrypted);
214
+ }
215
+
216
+ async findIndividualUserByAppUserId(appUserId) {
217
+ const doc = await findOne(this.prisma, 'User', {
218
+ type: 'INDIVIDUAL',
219
+ appUserId,
220
+ });
221
+ const decrypted = await this.encryptionService.decryptFields(
222
+ 'User',
223
+ doc
224
+ );
225
+ return this._mapUser(decrypted);
226
+ }
227
+
228
+ async findOrganizationUserByAppOrgId(appOrgId) {
229
+ const doc = await findOne(this.prisma, 'User', {
230
+ type: 'ORGANIZATION',
231
+ appOrgId,
232
+ });
233
+ const decrypted = await this.encryptionService.decryptFields(
234
+ 'User',
235
+ doc
236
+ );
237
+ return this._mapUser(decrypted);
238
+ }
239
+
240
+ async findIndividualUserByEmail(email) {
241
+ const doc = await findOne(this.prisma, 'User', {
242
+ type: 'INDIVIDUAL',
243
+ email,
244
+ });
245
+ const decrypted = await this.encryptionService.decryptFields(
246
+ 'User',
247
+ doc
248
+ );
249
+ return this._mapUser(decrypted);
250
+ }
251
+
252
+ async updateIndividualUser(userId, updates) {
253
+ const objectId = toObjectId(userId);
254
+ if (!objectId) return null;
255
+
256
+ const payload = await this._prepareUpdatePayload(updates);
257
+ payload.updatedAt = new Date();
258
+
259
+ // Encrypt sensitive fields before update
260
+ const encryptedPayload = await this.encryptionService.encryptFields(
261
+ 'User',
262
+ payload
263
+ );
264
+
265
+ await updateOne(
266
+ this.prisma,
267
+ 'User',
268
+ { _id: objectId, type: 'INDIVIDUAL' },
269
+ { $set: encryptedPayload }
270
+ );
271
+
272
+ const updated = await findOne(this.prisma, 'User', { _id: objectId });
273
+
274
+ // Defensive check: verify document was found after update
275
+ if (!updated) {
276
+ throw new Error(
277
+ `Failed to update individual user: Document not found after update (userId ${userId}). ` +
278
+ 'This indicates a database consistency issue.'
279
+ );
280
+ }
281
+
282
+ const decrypted = await this.encryptionService.decryptFields(
283
+ 'User',
284
+ updated
285
+ );
286
+ return this._mapUser(decrypted);
287
+ }
288
+
289
+ async updateOrganizationUser(userId, updates) {
290
+ const objectId = toObjectId(userId);
291
+ if (!objectId) return null;
292
+
293
+ const payload = { ...updates, updatedAt: new Date() };
294
+
295
+ const encryptedPayload = await this.encryptionService.encryptFields(
296
+ 'User',
297
+ payload
298
+ );
299
+
300
+ await updateOne(
301
+ this.prisma,
302
+ 'User',
303
+ { _id: objectId, type: 'ORGANIZATION' },
304
+ { $set: encryptedPayload }
305
+ );
306
+
307
+ const updated = await findOne(this.prisma, 'User', { _id: objectId });
308
+
309
+ if (!updated) {
310
+ throw new Error(
311
+ `Failed to update organization user: Document not found after update (userId ${userId}). ` +
312
+ 'This indicates a database consistency issue.'
313
+ );
314
+ }
315
+
316
+ const decrypted = await this.encryptionService.decryptFields(
317
+ 'User',
318
+ updated
319
+ );
320
+ return this._mapUser(decrypted);
321
+ }
322
+
323
+ async deleteUser(userId) {
324
+ const objectId = toObjectId(userId);
325
+ if (!objectId) return false;
326
+
327
+ const result = await deleteOne(this.prisma, 'User', { _id: objectId });
328
+ const deleted = result?.n ?? 0;
329
+ return deleted > 0;
330
+ }
331
+
332
+ _mapUser(doc) {
333
+ if (!doc) {
334
+ log.warn('_mapUser received a null document', {
335
+ eventName: 'frigg.user.map_user_null',
336
+ });
337
+ return null;
338
+ }
339
+
340
+ // Use optional chaining for robustness
341
+ return {
342
+ id: fromObjectId(doc?._id),
343
+ type: doc?.type ?? null,
344
+ email: doc?.email ?? null,
345
+ username: doc?.username ?? null,
346
+ hashword: doc?.hashword ?? null,
347
+ appUserId: doc?.appUserId ?? null,
348
+ organizationId: doc?.organizationId
349
+ ? fromObjectId(doc.organizationId)
350
+ : null,
351
+ appOrgId: doc?.appOrgId ?? null,
352
+ name: doc?.name ?? null,
353
+ createdAt: this._parseDate(doc?.createdAt),
354
+ updatedAt: this._parseDate(doc?.updatedAt),
355
+ };
356
+ }
357
+
358
+ async _prepareUpdatePayload(updates = {}) {
359
+ const payload = { ...updates };
360
+
361
+ if (
362
+ payload.hashword !== undefined &&
363
+ payload.hashword !== null &&
364
+ payload.hashword !== ''
365
+ ) {
366
+ if (typeof payload.hashword !== 'string') {
367
+ throw new ClientSafeError('Password must be a string', 400);
368
+ }
369
+
370
+ if (payload.hashword.startsWith('$2')) {
371
+ throw new Error(
372
+ 'Password appears to be already hashed. Pass plain text password only.'
373
+ );
374
+ }
375
+
376
+ payload.hashword = await bcrypt.hash(payload.hashword, 10);
377
+ }
378
+
379
+ if (payload.organization !== undefined) {
380
+ payload.organizationId = toObjectId(payload.organization);
381
+ delete payload.organization;
382
+ }
383
+
384
+ if (payload.organizationId !== undefined) {
385
+ payload.organizationId = payload.organizationId
386
+ ? toObjectId(payload.organizationId)
387
+ : null;
388
+ }
389
+
390
+ return payload;
391
+ }
392
+
393
+ /**
394
+ * Parse date value safely, returning undefined for invalid dates
395
+ * @private
396
+ * @param {*} value - Date value from database
397
+ * @returns {Date|undefined} Valid Date object or undefined
398
+ */
399
+ _parseDate(value) {
400
+ if (!value) return undefined;
401
+ const date = new Date(value);
402
+ return isNaN(date.getTime()) ? undefined : date;
403
+ }
404
+
405
+ /**
406
+ * Link an individual user to an organization user
407
+ * @param {string} individualUserId - Individual user ID (MongoDB ObjectId string)
408
+ * @param {string} organizationUserId - Organization user ID (MongoDB ObjectId string)
409
+ * @returns {Promise<Object>} Updated individual user object
410
+ */
411
+ async linkIndividualToOrganization(individualUserId, organizationUserId) {
412
+ const doc = await updateOne(
413
+ this.prisma,
414
+ 'User',
415
+ { _id: toObjectId(individualUserId), type: 'INDIVIDUAL' },
416
+ { $set: { organizationId: toObjectId(organizationUserId) } }
417
+ );
418
+ const decrypted = await this.encryptionService.decryptFields(
419
+ 'User',
420
+ doc
421
+ );
422
+ return this._mapUser(decrypted);
423
+ }
424
+ }
425
+
426
+ module.exports = { UserRepositoryDocumentDB };
@@ -0,0 +1,52 @@
1
+ const { UserRepositoryMongo } = require('./user-repository-mongo');
2
+ const { UserRepositoryPostgres } = require('./user-repository-postgres');
3
+ const { UserRepositoryDocumentDB } = require('./user-repository-documentdb');
4
+ const databaseConfig = require('../../database/config');
5
+
6
+ /**
7
+ * User Repository Factory
8
+ * Creates the appropriate repository adapter based on database type
9
+ *
10
+ * Database-specific implementations:
11
+ * - MongoDB: Uses String IDs (ObjectId), no conversion needed
12
+ * - PostgreSQL: Uses Int IDs, converts String ↔ Int
13
+ *
14
+ * All repository methods return String IDs regardless of database type,
15
+ * ensuring application layer consistency.
16
+ *
17
+ * Usage:
18
+ * ```javascript
19
+ * const repository = createUserRepository();
20
+ * const user = await repository.findIndividualUserById(id); // ID is string
21
+ * const orgUser = await repository.findOrganizationUserById(id); // ID is string
22
+ * ```
23
+ *
24
+ * @returns {UserRepositoryInterface} Configured repository adapter
25
+ */
26
+ function createUserRepository() {
27
+ const dbType = databaseConfig.DB_TYPE;
28
+
29
+ switch (dbType) {
30
+ case 'mongodb':
31
+ return new UserRepositoryMongo();
32
+
33
+ case 'postgresql':
34
+ return new UserRepositoryPostgres();
35
+
36
+ case 'documentdb':
37
+ return new UserRepositoryDocumentDB();
38
+
39
+ default:
40
+ throw new Error(
41
+ `Unsupported DB_TYPE: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
42
+ );
43
+ }
44
+ }
45
+
46
+ module.exports = {
47
+ createUserRepository,
48
+ // Export adapters for direct testing
49
+ UserRepositoryMongo,
50
+ UserRepositoryPostgres,
51
+ UserRepositoryDocumentDB,
52
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * User Repository Interface
3
+ * Abstract base class defining the contract for user persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Note: Currently, User model has identical structure across MongoDB and PostgreSQL,
11
+ * so UserRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class UserRepositoryInterface {
17
+ /**
18
+ * Get session token from base64 buffer token
19
+ *
20
+ * @param {string} token - Base64 buffer token
21
+ * @returns {Promise<Object>} Session token object
22
+ * @abstract
23
+ */
24
+ async getSessionToken(token) {
25
+ throw new Error(
26
+ 'Method getSessionToken must be implemented by subclass'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Find organization user by ID
32
+ *
33
+ * @param {string|number} userId - User ID
34
+ * @returns {Promise<Object|null>} User object or null
35
+ * @abstract
36
+ */
37
+ async findOrganizationUserById(userId) {
38
+ throw new Error(
39
+ 'Method findOrganizationUserById must be implemented by subclass'
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Find individual user by ID
45
+ *
46
+ * @param {string|number} userId - User ID
47
+ * @returns {Promise<Object|null>} User object or null
48
+ * @abstract
49
+ */
50
+ async findIndividualUserById(userId) {
51
+ throw new Error(
52
+ 'Method findIndividualUserById must be implemented by subclass'
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Find all individual users linked to an organization user
58
+ *
59
+ * @param {string|number} organizationUserId - Organization user ID
60
+ * @returns {Promise<Object[]>} Array of individual user objects (empty if none)
61
+ * @abstract
62
+ */
63
+ async findIndividualUsersByOrganizationId(organizationUserId) {
64
+ throw new Error(
65
+ 'Method findIndividualUsersByOrganizationId must be implemented by subclass'
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Create token with expiration
71
+ *
72
+ * @param {string|number} userId - User ID
73
+ * @param {string} rawToken - Raw unhashed token
74
+ * @param {number} minutes - Minutes until expiration (default 120)
75
+ * @returns {Promise<string>} Base64 buffer token
76
+ * @abstract
77
+ */
78
+ async createToken(userId, rawToken, minutes = 120) {
79
+ throw new Error('Method createToken must be implemented by subclass');
80
+ }
81
+
82
+ /**
83
+ * Create individual user
84
+ *
85
+ * @param {Object} params - User creation parameters
86
+ * @returns {Promise<Object>} Created user object
87
+ * @abstract
88
+ */
89
+ async createIndividualUser(params) {
90
+ throw new Error(
91
+ 'Method createIndividualUser must be implemented by subclass'
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Create organization user
97
+ *
98
+ * @param {Object} params - Organization creation parameters
99
+ * @returns {Promise<Object>} Created organization object
100
+ * @abstract
101
+ */
102
+ async createOrganizationUser(params) {
103
+ throw new Error(
104
+ 'Method createOrganizationUser must be implemented by subclass'
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Find individual user by username
110
+ *
111
+ * @param {string} username - Username to search for
112
+ * @returns {Promise<Object|null>} User object or null
113
+ * @abstract
114
+ */
115
+ async findIndividualUserByUsername(username) {
116
+ throw new Error(
117
+ 'Method findIndividualUserByUsername must be implemented by subclass'
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Find individual user by app user ID
123
+ *
124
+ * @param {string} appUserId - App user ID to search for
125
+ * @returns {Promise<Object|null>} User object or null
126
+ * @abstract
127
+ */
128
+ async findIndividualUserByAppUserId(appUserId) {
129
+ throw new Error(
130
+ 'Method findIndividualUserByAppUserId must be implemented by subclass'
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Find organization user by app org ID
136
+ *
137
+ * @param {string} appOrgId - App organization ID to search for
138
+ * @returns {Promise<Object|null>} User object or null
139
+ * @abstract
140
+ */
141
+ async findOrganizationUserByAppOrgId(appOrgId) {
142
+ throw new Error(
143
+ 'Method findOrganizationUserByAppOrgId must be implemented by subclass'
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Find individual user by email
149
+ *
150
+ * @param {string} email - Email to search for
151
+ * @returns {Promise<Object|null>} User object or null
152
+ * @abstract
153
+ */
154
+ async findIndividualUserByEmail(email) {
155
+ throw new Error(
156
+ 'Method findIndividualUserByEmail must be implemented by subclass'
157
+ );
158
+ }
159
+
160
+ /**
161
+ * Update individual user
162
+ *
163
+ * @param {string|number} userId - User ID
164
+ * @param {Object} updates - Fields to update
165
+ * @returns {Promise<Object>} Updated user object
166
+ * @abstract
167
+ */
168
+ async updateIndividualUser(userId, updates) {
169
+ throw new Error(
170
+ 'Method updateIndividualUser must be implemented by subclass'
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Update organization user
176
+ *
177
+ * @param {string|number} userId - User ID
178
+ * @param {Object} updates - Fields to update
179
+ * @returns {Promise<Object>} Updated user object
180
+ * @abstract
181
+ */
182
+ async updateOrganizationUser(userId, updates) {
183
+ throw new Error(
184
+ 'Method updateOrganizationUser must be implemented by subclass'
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Delete user by ID
190
+ *
191
+ * @param {string|number} userId - User ID to delete
192
+ * @returns {Promise<boolean>} True if deleted successfully
193
+ * @abstract
194
+ */
195
+ async deleteUser(userId) {
196
+ throw new Error('Method deleteUser must be implemented by subclass');
197
+ }
198
+
199
+ /**
200
+ * Link an individual user to an organization user
201
+ *
202
+ * @param {string|number} individualUserId - Individual user ID
203
+ * @param {string|number} organizationUserId - Organization user ID
204
+ * @returns {Promise<Object>} Updated individual user object
205
+ * @abstract
206
+ */
207
+ async linkIndividualToOrganization(individualUserId, organizationUserId) {
208
+ throw new Error(
209
+ 'Method linkIndividualToOrganization must be implemented by subclass'
210
+ );
211
+ }
212
+ }
213
+
214
+ module.exports = { UserRepositoryInterface };