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

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 +238 -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 +92 -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,404 @@
1
+ /**
2
+ * Encryption Schema Registry
3
+ *
4
+ * Centralized registry defining which fields require encryption for each Prisma model.
5
+ * Database-agnostic, works identically for MongoDB and PostgreSQL.
6
+ * Extensible by integration developers via appDefinition.
7
+ *
8
+ * Field path format: 'fieldName' or 'parent.child.field' for nested JSON.
9
+ */
10
+
11
+ const { logger } = require('./logger');
12
+ const { registerDeniedKeys } = require('../../logs/denied-keys');
13
+
14
+ /**
15
+ * Core encryption schema (immutable - cannot be overridden by custom schemas)
16
+ */
17
+ const CORE_ENCRYPTION_SCHEMA = {
18
+ Credential: {
19
+ fields: [
20
+ 'data.access_token',
21
+ 'data.refresh_token',
22
+ 'data.id_token',
23
+ 'data.api_key',
24
+ 'data.apiKey',
25
+ 'data.API_KEY_VALUE',
26
+ 'data.password',
27
+ 'data.client_secret',
28
+ ],
29
+ },
30
+
31
+ IntegrationMapping: {
32
+ fields: ['mapping'],
33
+ },
34
+
35
+ User: {
36
+ fields: ['hashword'],
37
+ },
38
+
39
+ Token: {
40
+ fields: ['token'],
41
+ },
42
+ };
43
+
44
+ let customSchema = {};
45
+
46
+ /**
47
+ * Per-model write-side opt-out: fields registered here are NOT encrypted on
48
+ * write, but ARE still decrypted on read so legacy encrypted rows continue to
49
+ * deserialize. Lets apps migrate a model from encrypted to plain JSON without
50
+ * a data migration — touched rows naturally rewrite as plain on the next save,
51
+ * untouched rows stay encrypted-but-readable forever.
52
+ *
53
+ * Shape: `{ ModelName: ['field.path', ...] }`
54
+ */
55
+ let encryptionOptOut = {};
56
+
57
+ /**
58
+ * Validates a custom encryption schema
59
+ * @returns {{valid: boolean, errors: string[]}}
60
+ */
61
+ function validateCustomSchema(schema) {
62
+ const errors = [];
63
+
64
+ if (!schema || typeof schema !== 'object') {
65
+ errors.push('Custom schema must be an object');
66
+ return { valid: false, errors };
67
+ }
68
+
69
+ for (const [modelName, config] of Object.entries(schema)) {
70
+ if (typeof modelName !== 'string' || !modelName) {
71
+ errors.push(`Invalid model name: ${modelName}`);
72
+ continue;
73
+ }
74
+
75
+ if (!config || typeof config !== 'object') {
76
+ errors.push(`Model "${modelName}" must have a config object`);
77
+ continue;
78
+ }
79
+
80
+ if (!Array.isArray(config.fields)) {
81
+ errors.push(`Model "${modelName}" must have a "fields" array`);
82
+ continue;
83
+ }
84
+
85
+ for (const fieldPath of config.fields) {
86
+ if (typeof fieldPath !== 'string' || !fieldPath) {
87
+ errors.push(`Model "${modelName}" has invalid field path: ${fieldPath}`);
88
+ }
89
+
90
+ // Check if trying to override core fields
91
+ const coreFields = CORE_ENCRYPTION_SCHEMA[modelName]?.fields || [];
92
+ if (coreFields.includes(fieldPath)) {
93
+ errors.push(
94
+ `Cannot override core encrypted field "${fieldPath}" in model "${modelName}"`
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ return {
101
+ valid: errors.length === 0,
102
+ errors,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Registers a custom encryption schema from integration developer.
108
+ * Merges with core schema, prevents overriding core fields.
109
+ * @throws {Error} If schema validation fails
110
+ */
111
+ function registerCustomSchema(schema) {
112
+ if (!schema || Object.keys(schema).length === 0) {
113
+ return; // Nothing to register
114
+ }
115
+
116
+ const validation = validateCustomSchema(schema);
117
+ if (!validation.valid) {
118
+ throw new Error(
119
+ `Invalid custom encryption schema:\n- ${validation.errors.join('\n- ')}`
120
+ );
121
+ }
122
+
123
+ customSchema = { ...schema };
124
+ registerDeniedKeys(Object.values(schema).flatMap((config) => config.fields));
125
+ logger.info(
126
+ `Registered custom encryption schema for models: ${Object.keys(customSchema).join(', ')}`
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Extracts credential field paths from module definitions
132
+ * @param {Array} moduleDefinitions - Array of module definition objects
133
+ * @returns {Array<string>} Array of field paths with data. prefix
134
+ */
135
+ function extractCredentialFieldsFromModules(moduleDefinitions) {
136
+ const fields = [];
137
+
138
+ for (const moduleDef of moduleDefinitions) {
139
+ if (!moduleDef?.encryption?.credentialFields) {
140
+ continue;
141
+ }
142
+
143
+ const credentialFields = moduleDef.encryption.credentialFields;
144
+ if (!Array.isArray(credentialFields) || credentialFields.length === 0) {
145
+ continue;
146
+ }
147
+
148
+ for (const field of credentialFields) {
149
+ const prefixedField = field.startsWith('data.') ? field : `data.${field}`;
150
+ fields.push(prefixedField);
151
+ }
152
+ }
153
+
154
+ registerDeniedKeys(fields);
155
+ return [...new Set(fields)];
156
+ }
157
+
158
+ /**
159
+ * Loads and registers encryption schemas from API module definitions.
160
+ * Each module can declare credentialFields to encrypt in its encryption config.
161
+ *
162
+ * @param {Array} integrations - Array of integration classes with modules
163
+ */
164
+ function loadModuleEncryptionSchemas(integrations) {
165
+ if (!integrations) {
166
+ throw new Error('integrations parameter is required');
167
+ }
168
+
169
+ if (!Array.isArray(integrations)) {
170
+ throw new Error('integrations must be an array');
171
+ }
172
+
173
+ if (integrations.length === 0) {
174
+ return;
175
+ }
176
+
177
+ const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
178
+
179
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
180
+ const credentialFields = extractCredentialFieldsFromModules(moduleDefinitions);
181
+
182
+ if (credentialFields.length === 0) {
183
+ return;
184
+ }
185
+
186
+ const moduleSchema = {
187
+ Credential: {
188
+ fields: credentialFields
189
+ }
190
+ };
191
+
192
+ logger.info(
193
+ `Registering module-level encryption for ${credentialFields.length} credential fields`
194
+ );
195
+
196
+ registerCustomSchema(moduleSchema);
197
+ }
198
+
199
+ /**
200
+ * Loads and registers custom encryption schema from appDefinition.
201
+ * Gracefully handles cases where appDefinition is not available.
202
+ *
203
+ * This ensures that custom encryption schemas defined in the backend's index.js
204
+ * are registered before any repositories attempt to encrypt data.
205
+ *
206
+ * Used by both Prisma (MongoDB/PostgreSQL) and DocumentDB encryption services.
207
+ */
208
+ function loadCustomEncryptionSchema() {
209
+ try {
210
+ // Lazy require to avoid circular dependency issues
211
+ const path = require('node:path');
212
+ const { findNearestBackendPackageJson } = require('../../utils');
213
+
214
+ const backendPackagePath = findNearestBackendPackageJson();
215
+ if (!backendPackagePath) {
216
+ return; // No backend found, skip custom schema
217
+ }
218
+
219
+ const backendDir = path.dirname(backendPackagePath);
220
+ const backendIndexPath = path.join(backendDir, 'index.js');
221
+
222
+ const backendModule = require(backendIndexPath);
223
+ const appDefinition = backendModule?.Definition;
224
+
225
+ if (!appDefinition) {
226
+ return; // No app definition found
227
+ }
228
+
229
+ // Load app-level custom schema
230
+ const customSchema = appDefinition.encryption?.schema;
231
+ if (customSchema && Object.keys(customSchema).length > 0) {
232
+ registerCustomSchema(customSchema);
233
+ }
234
+
235
+ // Load app-level encryption opt-out — apps can declare fields they
236
+ // don't want encrypted on write (decryption on read still works,
237
+ // so legacy data remains readable).
238
+ const disable = appDefinition.encryption?.disable;
239
+ if (disable && Object.keys(disable).length > 0) {
240
+ registerEncryptionOptOut(disable);
241
+ }
242
+
243
+ // Load module-level encryption schemas from integrations
244
+ const integrations = appDefinition.integrations;
245
+ if (integrations && Array.isArray(integrations)) {
246
+ loadModuleEncryptionSchemas(integrations);
247
+ }
248
+ } catch (error) {
249
+ // Silently ignore errors - custom schema is optional
250
+ // This handles cases like:
251
+ // - Backend package.json not found (tests, standalone usage)
252
+ // - No appDefinition defined
253
+ // - No custom encryption schema specified
254
+ logger.debug('Could not load custom encryption schema:', error.message);
255
+ }
256
+ }
257
+
258
+ function getEncryptedFields(modelName) {
259
+ const coreFields = CORE_ENCRYPTION_SCHEMA[modelName]?.fields || [];
260
+ const customFields = customSchema[modelName]?.fields || [];
261
+ const allFields = [...coreFields, ...customFields];
262
+ return [...new Set(allFields)];
263
+ }
264
+
265
+ /**
266
+ * Validates an encryption opt-out config.
267
+ *
268
+ * Unlike custom schema validation, opt-out IS allowed to target paths that
269
+ * already live in CORE_ENCRYPTION_SCHEMA — that's the entire point.
270
+ *
271
+ * @param {Object} optOut - Map of `{ ModelName: ['field.path', ...] }`
272
+ * @returns {{valid: boolean, errors: string[]}}
273
+ */
274
+ function validateOptOut(optOut) {
275
+ const errors = [];
276
+
277
+ if (!optOut || typeof optOut !== 'object') {
278
+ errors.push('Encryption opt-out must be an object');
279
+ return { valid: false, errors };
280
+ }
281
+
282
+ for (const [modelName, fields] of Object.entries(optOut)) {
283
+ if (typeof modelName !== 'string' || !modelName) {
284
+ errors.push(`Invalid model name in opt-out: ${modelName}`);
285
+ continue;
286
+ }
287
+
288
+ if (!Array.isArray(fields)) {
289
+ errors.push(
290
+ `Model "${modelName}" opt-out must be an array of field paths`
291
+ );
292
+ continue;
293
+ }
294
+
295
+ for (const fieldPath of fields) {
296
+ if (typeof fieldPath !== 'string' || !fieldPath) {
297
+ errors.push(
298
+ `Model "${modelName}" has invalid opt-out field path: ${fieldPath}`
299
+ );
300
+ }
301
+ }
302
+ }
303
+
304
+ return { valid: errors.length === 0, errors };
305
+ }
306
+
307
+ /**
308
+ * Registers an encryption opt-out config. Listed fields will be skipped during
309
+ * encryption on write while still being eligible for decryption on read (so
310
+ * legacy encrypted rows still deserialize correctly).
311
+ *
312
+ * Intended call site: `appDefinition.encryption.disable` via
313
+ * `loadCustomEncryptionSchema`.
314
+ *
315
+ * @param {Object} optOut - Map of `{ ModelName: ['field.path', ...] }`
316
+ * @throws {Error} If opt-out validation fails
317
+ */
318
+ function registerEncryptionOptOut(optOut) {
319
+ if (!optOut || Object.keys(optOut).length === 0) {
320
+ return;
321
+ }
322
+
323
+ const validation = validateOptOut(optOut);
324
+ if (!validation.valid) {
325
+ throw new Error(
326
+ `Invalid encryption opt-out:\n- ${validation.errors.join('\n- ')}`
327
+ );
328
+ }
329
+
330
+ encryptionOptOut = { ...optOut };
331
+ logger.info(
332
+ `Registered encryption opt-out for models: ${Object.keys(
333
+ encryptionOptOut
334
+ ).join(', ')}`
335
+ );
336
+ }
337
+
338
+ /**
339
+ * Returns the field paths that should be encrypted when writing the given
340
+ * model. This is `getEncryptedFields` minus any paths the app has opted out
341
+ * of via `registerEncryptionOptOut`.
342
+ *
343
+ * Use this in the encrypt-on-write path of the FieldEncryptionService.
344
+ */
345
+ function getFieldsToEncryptOnWrite(modelName) {
346
+ const allFields = getEncryptedFields(modelName);
347
+ const optedOut = new Set(encryptionOptOut[modelName] || []);
348
+ if (optedOut.size === 0) return allFields;
349
+ return allFields.filter((path) => !optedOut.has(path));
350
+ }
351
+
352
+ /**
353
+ * Returns the field paths that should be checked for decryption when reading
354
+ * the given model. Always includes opted-out paths so legacy encrypted rows
355
+ * remain readable after an app opts a field out.
356
+ *
357
+ * `FieldEncryptionService._isEncrypted` already short-circuits for plain JSON
358
+ * values, so listing more fields than necessary here is harmless.
359
+ *
360
+ * Use this in the decrypt-on-read path of the FieldEncryptionService.
361
+ */
362
+ function getFieldsToDecryptOnRead(modelName) {
363
+ return getEncryptedFields(modelName);
364
+ }
365
+
366
+ /**
367
+ * Clears any registered encryption opt-outs. Test-helper; not intended for
368
+ * runtime use.
369
+ */
370
+ function resetEncryptionOptOut() {
371
+ encryptionOptOut = {};
372
+ }
373
+
374
+ function hasEncryptedFields(modelName) {
375
+ return getEncryptedFields(modelName).length > 0;
376
+ }
377
+
378
+ function getEncryptedModels() {
379
+ const coreModels = Object.keys(CORE_ENCRYPTION_SCHEMA);
380
+ const customModels = Object.keys(customSchema);
381
+ return [...new Set([...coreModels, ...customModels])];
382
+ }
383
+
384
+ function resetCustomSchema() {
385
+ customSchema = {};
386
+ }
387
+
388
+ module.exports = {
389
+ CORE_ENCRYPTION_SCHEMA,
390
+ getEncryptedFields,
391
+ getFieldsToEncryptOnWrite,
392
+ getFieldsToDecryptOnRead,
393
+ hasEncryptedFields,
394
+ getEncryptedModels,
395
+ registerCustomSchema,
396
+ registerEncryptionOptOut,
397
+ loadCustomEncryptionSchema,
398
+ loadModuleEncryptionSchemas,
399
+ extractCredentialFieldsFromModules,
400
+ validateCustomSchema,
401
+ validateOptOut,
402
+ resetCustomSchema,
403
+ resetEncryptionOptOut,
404
+ };
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Field Encryption Service
3
+ *
4
+ * Infrastructure layer service that orchestrates field-level encryption/decryption.
5
+ * Handles nested JSON paths (e.g., 'data.access_token') and bulk operations.
6
+ */
7
+ class FieldEncryptionService {
8
+ constructor({ cryptor, schema }) {
9
+ if (!cryptor) {
10
+ throw new Error('Cryptor instance required');
11
+ }
12
+ if (!schema || typeof schema.getEncryptedFields !== 'function') {
13
+ throw new Error('Schema with getEncryptedFields method required');
14
+ }
15
+
16
+ this.cryptor = cryptor;
17
+ this.schema = schema;
18
+ }
19
+
20
+ /**
21
+ * Resolve the field paths to encrypt on write. Prefers
22
+ * `schema.getFieldsToEncryptOnWrite` (which respects app opt-outs); falls
23
+ * back to `schema.getEncryptedFields` for backwards compatibility with
24
+ * older schema adapters.
25
+ * @private
26
+ */
27
+ _getWriteFields(modelName) {
28
+ if (typeof this.schema.getFieldsToEncryptOnWrite === 'function') {
29
+ return this.schema.getFieldsToEncryptOnWrite(modelName);
30
+ }
31
+ return this.schema.getEncryptedFields(modelName);
32
+ }
33
+
34
+ /**
35
+ * Resolve the field paths to attempt decryption on read. Prefers
36
+ * `schema.getFieldsToDecryptOnRead` (which IGNORES opt-outs so legacy
37
+ * encrypted rows still deserialize); falls back to
38
+ * `schema.getEncryptedFields` for backwards compatibility.
39
+ * @private
40
+ */
41
+ _getReadFields(modelName) {
42
+ if (typeof this.schema.getFieldsToDecryptOnRead === 'function') {
43
+ return this.schema.getFieldsToDecryptOnRead(modelName);
44
+ }
45
+ return this.schema.getEncryptedFields(modelName);
46
+ }
47
+
48
+ async encryptFields(modelName, document) {
49
+ if (!document || typeof document !== 'object') {
50
+ return document;
51
+ }
52
+
53
+ const fields = this._getWriteFields(modelName);
54
+ if (fields.length === 0) {
55
+ return document;
56
+ }
57
+
58
+ const encrypted = this._deepClone(document);
59
+
60
+ // Parallelize encryption of multiple fields
61
+ const encryptionPromises = fields.map(async (fieldPath) => {
62
+ const value = this._getNestedValue(encrypted, fieldPath);
63
+
64
+ if (this._shouldEncrypt(value)) {
65
+ const serializedValue = this._serializeForEncryption(value);
66
+ const encryptedValue = await this.cryptor.encrypt(serializedValue);
67
+ return { fieldPath, encryptedValue };
68
+ }
69
+ return null;
70
+ });
71
+
72
+ const results = await Promise.all(encryptionPromises);
73
+
74
+ // Apply encrypted values
75
+ for (const result of results) {
76
+ if (result) {
77
+ this._setNestedValue(encrypted, result.fieldPath, result.encryptedValue);
78
+ }
79
+ }
80
+
81
+ return encrypted;
82
+ }
83
+
84
+ async decryptFields(modelName, document) {
85
+ if (!document || typeof document !== 'object') {
86
+ return document;
87
+ }
88
+
89
+ const fields = this._getReadFields(modelName);
90
+ if (fields.length === 0) {
91
+ return document;
92
+ }
93
+
94
+ const decrypted = this._deepClone(document);
95
+
96
+ // Parallelize decryption of multiple fields
97
+ const decryptionPromises = fields.map(async (fieldPath) => {
98
+ const value = this._getNestedValue(decrypted, fieldPath);
99
+
100
+ if (this._isEncrypted(value)) {
101
+ const decryptedValue = await this.cryptor.decrypt(value);
102
+ const deserializedValue = this._deserializeAfterDecryption(decryptedValue);
103
+ return { fieldPath, decryptedValue: deserializedValue };
104
+ }
105
+ return null;
106
+ });
107
+
108
+ const results = await Promise.all(decryptionPromises);
109
+
110
+ // Apply decrypted values
111
+ for (const result of results) {
112
+ if (result) {
113
+ this._setNestedValue(decrypted, result.fieldPath, result.decryptedValue);
114
+ }
115
+ }
116
+
117
+ return decrypted;
118
+ }
119
+
120
+ async encryptFieldsInBulk(modelName, documents) {
121
+ if (!Array.isArray(documents)) {
122
+ return documents;
123
+ }
124
+
125
+ return Promise.all(
126
+ documents.map((doc) => this.encryptFields(modelName, doc))
127
+ );
128
+ }
129
+
130
+ async decryptFieldsInBulk(modelName, documents) {
131
+ if (!Array.isArray(documents)) {
132
+ return documents;
133
+ }
134
+
135
+ return Promise.all(
136
+ documents.map((doc) => this.decryptFields(modelName, doc))
137
+ );
138
+ }
139
+
140
+ _shouldEncrypt(value) {
141
+ return (
142
+ value !== null &&
143
+ value !== undefined &&
144
+ value !== '' &&
145
+ !this._isEncrypted(value)
146
+ );
147
+ }
148
+
149
+ _isEncrypted(value) {
150
+ if (typeof value !== 'string') {
151
+ return false;
152
+ }
153
+
154
+ const parts = value.split(':');
155
+ return parts.length >= 4;
156
+ }
157
+
158
+ _getNestedValue(obj, path) {
159
+ if (!obj || !path) {
160
+ return undefined;
161
+ }
162
+
163
+ return path.split('.').reduce((current, key) => {
164
+ return current?.[key];
165
+ }, obj);
166
+ }
167
+
168
+ _setNestedValue(obj, path, value) {
169
+ if (!obj || !path) {
170
+ return;
171
+ }
172
+
173
+ const keys = path.split('.');
174
+ const lastKey = keys.pop();
175
+
176
+ const target = keys.reduce((current, key) => {
177
+ if (!current[key] || typeof current[key] !== 'object') {
178
+ current[key] = {};
179
+ }
180
+ return current[key];
181
+ }, obj);
182
+
183
+ target[lastKey] = value;
184
+ }
185
+
186
+ _deepClone(obj) {
187
+ // Use structuredClone (Node.js 17+) for better performance
188
+ // Falls back to custom implementation for older Node versions
189
+ if (typeof structuredClone !== 'undefined') {
190
+ try {
191
+ return structuredClone(obj);
192
+ } catch {
193
+ // Fall through to custom implementation
194
+ }
195
+ }
196
+
197
+ // Custom fallback for older environments
198
+ if (obj === null || typeof obj !== 'object') {
199
+ return obj;
200
+ }
201
+
202
+ if (obj instanceof Date) {
203
+ return new Date(obj.getTime());
204
+ }
205
+
206
+ if (Array.isArray(obj)) {
207
+ return obj.map((item) => this._deepClone(item));
208
+ }
209
+
210
+ const cloned = {};
211
+ for (const key in obj) {
212
+ if (obj.hasOwnProperty(key)) {
213
+ cloned[key] = this._deepClone(obj[key]);
214
+ }
215
+ }
216
+
217
+ return cloned;
218
+ }
219
+
220
+ /**
221
+ * Serialize a value for encryption
222
+ * Objects/arrays are JSON stringified, primitives are converted to strings
223
+ * @private
224
+ */
225
+ _serializeForEncryption(value) {
226
+ if (typeof value === 'object' && value !== null) {
227
+ // JSON.stringify for objects and arrays
228
+ return JSON.stringify(value);
229
+ }
230
+ // For primitives (string, number, boolean), convert to string
231
+ return String(value);
232
+ }
233
+
234
+ /**
235
+ * Deserialize a value after decryption
236
+ * Attempts to parse as JSON, returns string if parsing fails
237
+ * @private
238
+ */
239
+ _deserializeAfterDecryption(value) {
240
+ if (typeof value !== 'string') {
241
+ return value;
242
+ }
243
+
244
+ // Try to parse as JSON
245
+ try {
246
+ return JSON.parse(value);
247
+ } catch {
248
+ // Not valid JSON, return as-is (likely was a plain string field)
249
+ return value;
250
+ }
251
+ }
252
+ }
253
+
254
+ module.exports = { FieldEncryptionService };