@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,147 @@
1
+ /**
2
+ * Database Configuration
3
+ * Manages configuration for Prisma ORM operations
4
+ */
5
+
6
+ /**
7
+ * Determines database type from environment or app definition
8
+ *
9
+ * Detection order:
10
+ * 1. DB_TYPE environment variable (set for migration handlers)
11
+ * 2. App definition (backend/index.js Definition.database configuration)
12
+ *
13
+ * @returns {'mongodb'|'postgresql'|'documentdb'} Database type
14
+ * @throws {Error} If database type cannot be determined or app definition missing
15
+ */
16
+ function getDatabaseType() {
17
+ // First, check DB_TYPE environment variable (migration handlers set this)
18
+ if (process.env.DB_TYPE) {
19
+ return process.env.DB_TYPE;
20
+ }
21
+
22
+ // Fallback: Load app definition
23
+ try {
24
+ const path = require('node:path');
25
+ const fs = require('node:fs');
26
+ const { findNearestBackendPackageJson } = require('../utils');
27
+
28
+ let backendIndexPath;
29
+ let database;
30
+ const backendPackagePath = findNearestBackendPackageJson();
31
+
32
+ if (!backendPackagePath) {
33
+ throw new Error(
34
+ '[Frigg] Cannot find backend package.json. ' +
35
+ 'Ensure backend/package.json exists in your project.'
36
+ );
37
+ }
38
+
39
+ const backendDir = path.dirname(backendPackagePath);
40
+ backendIndexPath = path.join(backendDir, 'index.js');
41
+
42
+ if (!fs.existsSync(backendIndexPath)) {
43
+ throw new Error(
44
+ `[Frigg] Backend index.js not found at ${backendIndexPath}. ` +
45
+ 'Ensure backend/index.js exists with a Definition export.'
46
+ );
47
+ }
48
+
49
+ let backendModule;
50
+ try {
51
+ backendModule = require(backendIndexPath);
52
+ } catch (requireError) {
53
+ // Extract the actual file with the error from the stack trace
54
+ // Skip internal Node.js files (node:internal/*) and find first user file
55
+ let errorFile = 'unknown file';
56
+ const stackLines = requireError.stack?.split('\n') || [];
57
+
58
+ for (const line of stackLines) {
59
+ // Match file paths in stack trace, excluding node:internal
60
+ const match = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/at ([^(]+\.js):\d+:\d+/);
61
+ if (match && match[1] && !match[1].includes('node:internal')) {
62
+ errorFile = match[1];
63
+ break;
64
+ }
65
+ }
66
+
67
+ // Provide better error context for syntax/runtime errors
68
+ throw new Error(
69
+ `[Frigg] Failed to load app definition from ${backendIndexPath}\n` +
70
+ `Error: ${requireError.message}\n` +
71
+ `File with error: ${errorFile}\n` +
72
+ `\nFull stack trace:\n${requireError.stack}\n\n` +
73
+ 'This error occurred while loading your app definition or its dependencies. ' +
74
+ 'Check the file listed above for syntax errors (trailing commas, missing brackets, etc.)'
75
+ );
76
+ }
77
+
78
+ database = backendModule?.Definition?.database;
79
+
80
+ if (!database) {
81
+ throw new Error(
82
+ '[Frigg] App definition missing database configuration. ' +
83
+ `Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to ${backendIndexPath}`
84
+ );
85
+ }
86
+
87
+ // Determine database type from enabled database
88
+ // Priority order: postgres > mongoDB > documentDB
89
+ if (database.postgres?.enable === true) {
90
+ return 'postgresql';
91
+ }
92
+ if (database.mongoDB?.enable === true) {
93
+ return 'mongodb';
94
+ }
95
+ if (database.documentDB?.enable === true) {
96
+ return 'documentdb';
97
+ }
98
+
99
+ throw new Error(
100
+ '[Frigg] No database enabled in app definition. ' +
101
+ 'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
102
+ );
103
+ } catch (error) {
104
+ // Re-throw with context if it's our error
105
+ if (error.message.includes('[Frigg]')) {
106
+ throw error;
107
+ }
108
+ // Wrap unexpected errors
109
+ throw new Error(
110
+ `[Frigg] Failed to determine database type: ${error.message}`
111
+ );
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Cached database type (lazy evaluation)
117
+ * @type {'mongodb'|'postgresql'|'documentdb'|null}
118
+ */
119
+ let cachedDbType = null;
120
+
121
+ /**
122
+ * Enable Prisma debug logging
123
+ * Set PRISMA_LOG_LEVEL to comma-separated list: query,info,warn,error
124
+ * @type {string}
125
+ */
126
+ const PRISMA_LOG_LEVEL = process.env.PRISMA_LOG_LEVEL || 'error,warn';
127
+
128
+ module.exports = {
129
+ getDatabaseType, // Export for testing and direct use
130
+ PRISMA_LOG_LEVEL,
131
+ };
132
+
133
+ /**
134
+ * Lazy-evaluated database type determined from app definition
135
+ * Only evaluates when accessed, preventing module load failures in test environments
136
+ * @type {'mongodb'|'postgresql'|'documentdb'}
137
+ */
138
+ Object.defineProperty(module.exports, 'DB_TYPE', {
139
+ get() {
140
+ if (cachedDbType === null) {
141
+ cachedDbType = getDatabaseType();
142
+ }
143
+ return cachedDbType;
144
+ },
145
+ enumerable: true,
146
+ configurable: true
147
+ });
@@ -0,0 +1,334 @@
1
+ const { Cryptor } = require('../encrypt/Cryptor');
2
+ const {
3
+ getEncryptedFields,
4
+ getFieldsToEncryptOnWrite,
5
+ loadCustomEncryptionSchema,
6
+ } = require('./encryption/encryption-schema-registry');
7
+
8
+ /**
9
+ * Encryption service specifically for DocumentDB repositories
10
+ * that use $runCommandRaw and bypass Prisma Client Extensions.
11
+ *
12
+ * Provides document-level encryption/decryption, handling nested fields
13
+ * according to the encryption schema registry.
14
+ *
15
+ * @class DocumentDBEncryptionService
16
+ * @example
17
+ * const service = new DocumentDBEncryptionService();
18
+ *
19
+ * // Encrypt before write
20
+ * const encrypted = await service.encryptFields('Credential', document);
21
+ * await insertOne(prisma, 'Credential', encrypted);
22
+ *
23
+ * // Decrypt after read
24
+ * const doc = await findOne(prisma, 'Credential', filter);
25
+ * const decrypted = await service.decryptFields('Credential', doc);
26
+ */
27
+ class DocumentDBEncryptionService {
28
+ /**
29
+ * @param {Object} options - Configuration options
30
+ * @param {Cryptor} [options.cryptor] - Optional Cryptor instance for dependency injection (useful for testing)
31
+ */
32
+ constructor({ cryptor = null } = {}) {
33
+ if (cryptor) {
34
+ // Dependency injection - use provided Cryptor (for testing)
35
+ this.cryptor = cryptor;
36
+ this.enabled = true;
37
+ } else {
38
+ // Default behavior - create Cryptor from environment
39
+ this._initializeCryptor();
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Initialize Cryptor with environment-based configuration.
45
+ * Matches the logic from @friggframework/core/database/prisma.js
46
+ *
47
+ * Encryption is bypassed in dev/test/local stages.
48
+ * Production uses AWS KMS (if available) or AES encryption.
49
+ *
50
+ * @private
51
+ */
52
+ _initializeCryptor() {
53
+ // Load custom encryption schema from app definition BEFORE checking configuration
54
+ // This ensures custom fields (like User.username) are registered before any encryption operations
55
+ loadCustomEncryptionSchema();
56
+
57
+ // Match logic from packages/core/database/prisma.js
58
+ const stage = process.env.STAGE || process.env.NODE_ENV || 'development';
59
+ const bypassEncryption = ['dev', 'test', 'local'].includes(stage.toLowerCase());
60
+
61
+ if (bypassEncryption) {
62
+ this.cryptor = null;
63
+ this.enabled = false;
64
+ return;
65
+ }
66
+
67
+ // Determine encryption method (ensure boolean values)
68
+ const hasKMS = !!(process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== '');
69
+ const hasAES = !!(process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== '');
70
+
71
+ if (!hasKMS && !hasAES) {
72
+ console.warn('[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.');
73
+ this.cryptor = null;
74
+ this.enabled = false;
75
+ return;
76
+ }
77
+
78
+ // KMS takes precedence over AES
79
+ const shouldUseAws = hasKMS;
80
+ this.cryptor = new Cryptor({ shouldUseAws });
81
+ this.enabled = true;
82
+ }
83
+
84
+ /**
85
+ * Encrypt sensitive fields in a document before storing to DocumentDB.
86
+ *
87
+ * Reads field paths from encryption-schema-registry.js and encrypts
88
+ * only the fields defined for the given model.
89
+ *
90
+ * @param {string} modelName - Model name from schema registry (e.g., 'User', 'Credential')
91
+ * @param {Object} document - Document to encrypt
92
+ * @returns {Promise<Object>} - New document with encrypted fields (original unchanged)
93
+ *
94
+ * @example
95
+ * const plainDoc = {
96
+ * userId: '123',
97
+ * data: { access_token: 'plain_secret' }
98
+ * };
99
+ * const encrypted = await service.encryptFields('Credential', plainDoc);
100
+ * // encrypted.data.access_token = "keyId:iv:cipher:encKey"
101
+ */
102
+ async encryptFields(modelName, document) {
103
+ // Bypass if encryption disabled
104
+ if (!this.enabled || !this.cryptor) {
105
+ return document;
106
+ }
107
+
108
+ // Validate input
109
+ if (!document || typeof document !== 'object') {
110
+ return document;
111
+ }
112
+
113
+ // Get encrypted fields from registry
114
+ const encryptedFieldsConfig = getFieldsToEncryptOnWrite(modelName);
115
+ if (!encryptedFieldsConfig || encryptedFieldsConfig.length === 0) {
116
+ return document;
117
+ }
118
+
119
+ // Deep clone to prevent mutation (preserves Date, RegExp, Buffer)
120
+ const result = structuredClone(document);
121
+
122
+ // Encrypt each field path
123
+ for (const fieldPath of encryptedFieldsConfig) {
124
+ await this._encryptFieldPath(result, fieldPath, modelName);
125
+ }
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Decrypt sensitive fields in a document after reading from DocumentDB.
132
+ *
133
+ * Reads field paths from encryption-schema-registry.js and decrypts
134
+ * only the fields defined for the given model.
135
+ *
136
+ * @param {string} modelName - Model name from schema registry
137
+ * @param {Object} document - Document to decrypt
138
+ * @returns {Promise<Object>} - New document with decrypted fields (original unchanged)
139
+ *
140
+ * @example
141
+ * const encryptedDoc = {
142
+ * userId: '123',
143
+ * data: { access_token: 'keyId:iv:cipher:encKey' }
144
+ * };
145
+ * const decrypted = await service.decryptFields('Credential', encryptedDoc);
146
+ * // decrypted.data.access_token = "plain_secret"
147
+ */
148
+ async decryptFields(modelName, document) {
149
+ // Bypass if encryption disabled
150
+ if (!this.enabled || !this.cryptor) {
151
+ return document;
152
+ }
153
+
154
+ // Validate input
155
+ if (!document || typeof document !== 'object') {
156
+ return document;
157
+ }
158
+
159
+ // Get encrypted fields from registry
160
+ const encryptedFieldsConfig = getEncryptedFields(modelName);
161
+ if (!encryptedFieldsConfig || encryptedFieldsConfig.length === 0) {
162
+ return document;
163
+ }
164
+
165
+ // Deep clone to prevent mutation (preserves Date, RegExp, Buffer)
166
+ const result = structuredClone(document);
167
+
168
+ // Decrypt each field path
169
+ for (const fieldPath of encryptedFieldsConfig) {
170
+ await this._decryptFieldPath(result, fieldPath, modelName);
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ /**
177
+ * Encrypt a specific field path in a document (handles nested fields).
178
+ *
179
+ * @private
180
+ * @param {Object} document - Document to modify (mutated in place)
181
+ * @param {string} fieldPath - Field path from schema registry (e.g., 'data.access_token')
182
+ * @param {string} modelName - For error logging context
183
+ */
184
+ async _encryptFieldPath(document, fieldPath, modelName) {
185
+ // Parse field path
186
+ const parts = fieldPath.split('.');
187
+
188
+ // Navigate to parent object
189
+ let current = document;
190
+ for (let i = 0; i < parts.length - 1; i++) {
191
+ if (!current[parts[i]]) {
192
+ // Path doesn't exist, nothing to encrypt
193
+ return;
194
+ }
195
+ current = current[parts[i]];
196
+ }
197
+
198
+ // Get field name and value
199
+ const fieldName = parts[parts.length - 1];
200
+ const value = current[fieldName];
201
+
202
+ // Skip if already encrypted or empty
203
+ if (!value || this._isEncryptedValue(value)) {
204
+ return;
205
+ }
206
+
207
+ try {
208
+ // Convert to string if needed
209
+ const stringValue = typeof value === 'string'
210
+ ? value
211
+ : JSON.stringify(value);
212
+
213
+ // Encrypt using Cryptor
214
+ current[fieldName] = await this.cryptor.encrypt(stringValue);
215
+ } catch (error) {
216
+ console.error(`[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`, error.message);
217
+ throw error;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Decrypt a specific field path in a document (handles nested fields).
223
+ *
224
+ * @private
225
+ * @param {Object} document - Document to modify (mutated in place)
226
+ * @param {string} fieldPath - Field path from schema registry
227
+ * @param {string} modelName - For error logging context
228
+ */
229
+ async _decryptFieldPath(document, fieldPath, modelName) {
230
+ // Parse field path
231
+ const parts = fieldPath.split('.');
232
+
233
+ // Navigate to parent object
234
+ let current = document;
235
+ for (let i = 0; i < parts.length - 1; i++) {
236
+ if (!current[parts[i]]) {
237
+ // Path doesn't exist, nothing to decrypt
238
+ return;
239
+ }
240
+ current = current[parts[i]];
241
+ }
242
+
243
+ // Get field name and encrypted value
244
+ const fieldName = parts[parts.length - 1];
245
+ const encryptedValue = current[fieldName];
246
+
247
+ // Skip if not encrypted format
248
+ if (!encryptedValue || !this._isEncryptedValue(encryptedValue)) {
249
+ return;
250
+ }
251
+
252
+ try {
253
+ // Decrypt using Cryptor
254
+ const decryptedString = await this.cryptor.decrypt(encryptedValue);
255
+
256
+ // Try to parse as JSON (for objects/arrays)
257
+ try {
258
+ current[fieldName] = JSON.parse(decryptedString);
259
+ } catch {
260
+ // Not JSON, return as string
261
+ current[fieldName] = decryptedString;
262
+ }
263
+ } catch (error) {
264
+ const errorContext = {
265
+ modelName,
266
+ fieldPath,
267
+ encryptedValuePrefix: encryptedValue.substring(0, 20),
268
+ errorMessage: error.message
269
+ };
270
+
271
+ console.error(
272
+ `[DocumentDBEncryptionService] Failed to decrypt ${modelName}.${fieldPath}:`,
273
+ JSON.stringify(errorContext)
274
+ );
275
+
276
+ // Throw error to fail fast - don't silently corrupt data
277
+ throw new Error(`Decryption failed for ${modelName}.${fieldPath}: ${error.message}`);
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Check if a value is in encrypted format.
283
+ *
284
+ * Encrypted format: "keyId:iv:cipher:encKey" (envelope encryption)
285
+ * All parts are base64-encoded strings.
286
+ *
287
+ * @private
288
+ * @param {any} value - Value to check
289
+ * @returns {boolean} - True if value is encrypted
290
+ *
291
+ * @example
292
+ * _isEncryptedValue("plain_text") // false
293
+ * _isEncryptedValue("YWVzLWtleS0x:TXlJVkhlcmU=:QWN0dWFsQ2lwaGVy:RW5jcnlwdGVk") // true
294
+ * _isEncryptedValue(null) // false
295
+ * _isEncryptedValue({}) // false
296
+ */
297
+ _isEncryptedValue(value) {
298
+ // Must be string
299
+ if (typeof value !== 'string') {
300
+ return false;
301
+ }
302
+
303
+ // Must have exactly 4 colon-separated parts
304
+ const parts = value.split(':');
305
+ if (parts.length !== 4) {
306
+ return false;
307
+ }
308
+
309
+ // Enhanced validation: check for base64 pattern
310
+ // This prevents false positives on URLs, connection strings, etc.
311
+ const base64Pattern = /^[A-Za-z0-9+/=]+$/;
312
+
313
+ // All parts should be base64-encoded
314
+ if (!parts.every(part => base64Pattern.test(part))) {
315
+ return false;
316
+ }
317
+
318
+ // Encrypted values should be sufficiently long to be valid
319
+ // Real encrypted values from Cryptor are always >50 chars due to envelope encryption format:
320
+ // - keyId (base64): ~12 chars minimum
321
+ // - iv (base64): ~24 chars for 16-byte IV
322
+ // - ciphertext (base64): varies, minimum ~16 chars for small values
323
+ // - encryptedKey (base64): ~44 chars for 32-byte data key
324
+ // Total minimum: ~96 chars, so 50 is a safe lower bound
325
+ // This prevents false positives on non-encrypted strings that happen to have 4 colons
326
+ if (value.length < 50) {
327
+ return false;
328
+ }
329
+
330
+ return true;
331
+ }
332
+ }
333
+
334
+ module.exports = { DocumentDBEncryptionService };
@@ -0,0 +1,196 @@
1
+ const { ObjectId } = require('bson');
2
+
3
+ function toObjectId(value) {
4
+ if (value === null || value === undefined || value === '') return undefined;
5
+ if (value instanceof ObjectId) return value;
6
+ if (typeof value === 'object' && value.$oid) return new ObjectId(value.$oid);
7
+ if (typeof value === 'string') return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
8
+ return undefined;
9
+ }
10
+
11
+ function toObjectIdArray(values) {
12
+ if (!Array.isArray(values)) return [];
13
+ return values.map(toObjectId).filter(Boolean);
14
+ }
15
+
16
+ function fromObjectId(value) {
17
+ if (value instanceof ObjectId) return value.toHexString();
18
+ if (typeof value === 'object' && value !== null && value.$oid) return value.$oid;
19
+ if (typeof value === 'string') return value;
20
+ return value === undefined || value === null ? value : String(value);
21
+ }
22
+
23
+ async function findMany(client, collection, filter = {}, options = {}) {
24
+ const command = { find: collection, filter };
25
+ if (options.projection) command.projection = options.projection;
26
+ if (options.sort) command.sort = options.sort;
27
+ if (options.limit) command.limit = options.limit;
28
+ const result = await client.$runCommandRaw(command);
29
+ return result?.cursor?.firstBatch || [];
30
+ }
31
+
32
+ async function findOne(client, collection, filter = {}, options = {}) {
33
+ const docs = await findMany(client, collection, filter, { ...options, limit: 1 });
34
+ return docs[0] || null;
35
+ }
36
+
37
+ async function insertOne(client, collection, document) {
38
+ // Generate ObjectId if not present (MongoDB raw insert doesn't return insertedIds)
39
+ const _id = document._id || new ObjectId();
40
+ const docWithId = { ...document, _id };
41
+
42
+ const result = await client.$runCommandRaw({
43
+ insert: collection,
44
+ documents: [docWithId],
45
+ });
46
+
47
+ // Validate insert succeeded
48
+ if (result.ok !== 1) {
49
+ throw new Error(
50
+ `Insert command failed for collection '${collection}': ${JSON.stringify(result)}`
51
+ );
52
+ }
53
+
54
+ // Check for write errors (duplicate keys, validation errors, etc.)
55
+ if (result.writeErrors && result.writeErrors.length > 0) {
56
+ const error = result.writeErrors[0];
57
+ const errorMsg = `Insert failed in '${collection}': ${error.errmsg} (code: ${error.code})`;
58
+
59
+ // Provide helpful context for common errors
60
+ if (error.code === 11000) {
61
+ throw Object.assign(
62
+ new Error(`${errorMsg} - Duplicate key violation`),
63
+ { code: 11000 }
64
+ );
65
+ }
66
+ throw new Error(errorMsg);
67
+ }
68
+
69
+ // Verify exactly one document was inserted
70
+ if (result.n !== 1) {
71
+ throw new Error(
72
+ `Expected to insert 1 document into '${collection}', but inserted ${result.n}. ` +
73
+ `Result: ${JSON.stringify(result)}`
74
+ );
75
+ }
76
+
77
+ return _id;
78
+ }
79
+
80
+ async function updateOne(client, collection, filter, update, options = {}) {
81
+ const updates = [{
82
+ q: filter,
83
+ u: update,
84
+ upsert: Boolean(options.upsert),
85
+ }];
86
+ if (options.arrayFilters) updates[0].arrayFilters = options.arrayFilters;
87
+ const result = await client.$runCommandRaw({
88
+ update: collection,
89
+ updates,
90
+ });
91
+ return result;
92
+ }
93
+
94
+ async function deleteOne(client, collection, filter) {
95
+ return client.$runCommandRaw({
96
+ delete: collection,
97
+ deletes: [
98
+ {
99
+ q: filter,
100
+ limit: 1,
101
+ },
102
+ ],
103
+ });
104
+ }
105
+
106
+ async function deleteMany(client, collection, filter) {
107
+ return client.$runCommandRaw({
108
+ delete: collection,
109
+ deletes: [
110
+ {
111
+ q: filter,
112
+ limit: 0,
113
+ },
114
+ ],
115
+ });
116
+ }
117
+
118
+ async function aggregate(client, collection, pipeline) {
119
+ const result = await client.$runCommandRaw({
120
+ aggregate: collection,
121
+ pipeline,
122
+ cursor: {},
123
+ });
124
+ return result?.cursor?.firstBatch || [];
125
+ }
126
+
127
+ // findMany/aggregate return ONLY the first batch (~101 docs); the drained variants below follow the cursor to completion so full scans don't silently truncate.
128
+ const DRAIN_BATCH_SIZE = 1000;
129
+ const MAX_DRAIN_BATCHES = 100000;
130
+
131
+ function isCursorOpen(id) {
132
+ if (id === undefined || id === null) return false;
133
+ if (typeof id === 'number') return id !== 0;
134
+ if (typeof id === 'bigint') return id !== 0n;
135
+ // Extended JSON can surface a 64-bit cursor id as { $numberLong: "..." }.
136
+ if (typeof id === 'object' && id.$numberLong !== undefined) {
137
+ return id.$numberLong !== '0';
138
+ }
139
+ return String(id) !== '0';
140
+ }
141
+
142
+ async function drainCursor(client, collection, firstResult) {
143
+ const cursor = firstResult?.cursor || {};
144
+ const docs = [...(cursor.firstBatch || [])];
145
+ let cursorId = cursor.id;
146
+ let batches = 0;
147
+
148
+ while (isCursorOpen(cursorId) && batches < MAX_DRAIN_BATCHES) {
149
+ batches += 1;
150
+ const next = await client.$runCommandRaw({
151
+ getMore: cursorId,
152
+ collection,
153
+ batchSize: DRAIN_BATCH_SIZE,
154
+ });
155
+ const nextCursor = next?.cursor || {};
156
+ const nextBatch = nextCursor.nextBatch || [];
157
+ docs.push(...nextBatch);
158
+ cursorId = nextCursor.id;
159
+ if (nextBatch.length === 0) break;
160
+ }
161
+ return docs;
162
+ }
163
+
164
+ async function findManyDrained(client, collection, filter = {}, options = {}) {
165
+ const command = { find: collection, filter, batchSize: DRAIN_BATCH_SIZE };
166
+ if (options.projection) command.projection = options.projection;
167
+ if (options.sort) command.sort = options.sort;
168
+ const first = await client.$runCommandRaw(command);
169
+ return drainCursor(client, collection, first);
170
+ }
171
+
172
+ async function aggregateDrained(client, collection, pipeline, options = {}) {
173
+ const first = await client.$runCommandRaw({
174
+ aggregate: collection,
175
+ pipeline,
176
+ cursor: { batchSize: DRAIN_BATCH_SIZE },
177
+ ...options,
178
+ });
179
+ return drainCursor(client, collection, first);
180
+ }
181
+
182
+ module.exports = {
183
+ toObjectId,
184
+ toObjectIdArray,
185
+ fromObjectId,
186
+ findMany,
187
+ findOne,
188
+ insertOne,
189
+ updateOne,
190
+ deleteOne,
191
+ deleteMany,
192
+ aggregate,
193
+ findManyDrained,
194
+ aggregateDrained,
195
+ };
196
+