@friggframework/core 2.0.0-next.11 → 2.0.0-next.110

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 (375) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +999 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +359 -0
  16. package/application/commands/credential-commands.js +262 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +356 -0
  19. package/application/commands/integration-mapping-commands.js +25 -0
  20. package/application/commands/process-commands.js +135 -0
  21. package/application/commands/report-commands.js +188 -0
  22. package/application/commands/scheduler-commands.js +263 -0
  23. package/application/commands/usage-commands.js +56 -0
  24. package/application/commands/user-commands.js +312 -0
  25. package/application/index.js +83 -0
  26. package/artifacts/repositories/artifact-repository-factory.js +19 -0
  27. package/artifacts/repositories/artifact-repository-interface.js +27 -0
  28. package/artifacts/repositories/artifact-repository-local.js +42 -0
  29. package/artifacts/repositories/artifact-repository-s3.js +61 -0
  30. package/assertions/index.js +0 -3
  31. package/core/CLAUDE.md +700 -0
  32. package/core/Worker.js +60 -24
  33. package/core/create-handler.js +189 -5
  34. package/core/parameters-to-env.js +257 -0
  35. package/core/ssm-preload.mjs +34 -0
  36. package/credential/repositories/credential-active-type.js +32 -0
  37. package/credential/repositories/credential-repository-documentdb.js +355 -0
  38. package/credential/repositories/credential-repository-factory.js +54 -0
  39. package/credential/repositories/credential-repository-interface.js +113 -0
  40. package/credential/repositories/credential-repository-mongo.js +294 -0
  41. package/credential/repositories/credential-repository-postgres.js +312 -0
  42. package/credential/repositories/credential-repository.js +300 -0
  43. package/credential/use-cases/get-credential-for-user.js +25 -0
  44. package/credential/use-cases/update-authentication-status.js +15 -0
  45. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  46. package/database/adapters/lambda-invoker.js +97 -0
  47. package/database/config.js +154 -0
  48. package/database/documentdb-encryption-service.js +330 -0
  49. package/database/documentdb-utils.js +192 -0
  50. package/database/encryption/README.md +839 -0
  51. package/database/encryption/documentdb-encryption-service.md +3575 -0
  52. package/database/encryption/encryption-schema-registry.js +401 -0
  53. package/database/encryption/field-encryption-service.js +254 -0
  54. package/database/encryption/logger.js +79 -0
  55. package/database/encryption/prisma-encryption-extension.js +230 -0
  56. package/database/index.js +21 -21
  57. package/database/prisma.js +182 -0
  58. package/database/repositories/health-check-repository-documentdb.js +138 -0
  59. package/database/repositories/health-check-repository-factory.js +48 -0
  60. package/database/repositories/health-check-repository-interface.js +82 -0
  61. package/database/repositories/health-check-repository-mongodb.js +89 -0
  62. package/database/repositories/health-check-repository-postgres.js +82 -0
  63. package/database/repositories/migration-status-repository-s3.js +137 -0
  64. package/database/use-cases/check-database-health-use-case.js +29 -0
  65. package/database/use-cases/check-database-state-use-case.js +81 -0
  66. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  67. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  68. package/database/use-cases/get-migration-status-use-case.js +93 -0
  69. package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
  70. package/database/use-cases/run-database-migration-use-case.js +139 -0
  71. package/database/use-cases/test-encryption-use-case.js +253 -0
  72. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  73. package/database/utils/mongodb-collection-utils.js +94 -0
  74. package/database/utils/mongodb-schema-init.js +108 -0
  75. package/database/utils/prisma-runner.js +491 -0
  76. package/database/utils/prisma-schema-parser.js +182 -0
  77. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  78. package/encrypt/Cryptor.js +34 -168
  79. package/encrypt/index.js +1 -2
  80. package/errors/client-safe-error.js +26 -0
  81. package/errors/fetch-error.js +15 -7
  82. package/errors/index.js +2 -0
  83. package/generated/prisma-mongodb/client.d.ts +1 -0
  84. package/generated/prisma-mongodb/client.js +5 -0
  85. package/generated/prisma-mongodb/default.d.ts +1 -0
  86. package/generated/prisma-mongodb/default.js +5 -0
  87. package/generated/prisma-mongodb/edge.d.ts +1 -0
  88. package/generated/prisma-mongodb/edge.js +386 -0
  89. package/generated/prisma-mongodb/index-browser.js +368 -0
  90. package/generated/prisma-mongodb/index.d.ts +27065 -0
  91. package/generated/prisma-mongodb/index.js +411 -0
  92. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  93. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  94. package/generated/prisma-mongodb/package.json +183 -0
  95. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  96. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  97. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  98. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  99. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  100. package/generated/prisma-mongodb/runtime/library.js +147 -0
  101. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  102. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  103. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  104. package/generated/prisma-mongodb/schema.prisma +455 -0
  105. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  106. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  107. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  108. package/generated/prisma-mongodb/wasm.js +393 -0
  109. package/generated/prisma-postgresql/client.d.ts +1 -0
  110. package/generated/prisma-postgresql/client.js +5 -0
  111. package/generated/prisma-postgresql/default.d.ts +1 -0
  112. package/generated/prisma-postgresql/default.js +5 -0
  113. package/generated/prisma-postgresql/edge.d.ts +1 -0
  114. package/generated/prisma-postgresql/edge.js +407 -0
  115. package/generated/prisma-postgresql/index-browser.js +389 -0
  116. package/generated/prisma-postgresql/index.d.ts +29904 -0
  117. package/generated/prisma-postgresql/index.js +432 -0
  118. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  119. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  120. package/generated/prisma-postgresql/package.json +183 -0
  121. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  122. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  123. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  124. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  125. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  126. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  127. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  128. package/generated/prisma-postgresql/runtime/library.js +147 -0
  129. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  130. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  131. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  132. package/generated/prisma-postgresql/schema.prisma +436 -0
  133. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  134. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  135. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  136. package/generated/prisma-postgresql/wasm.js +414 -0
  137. package/handlers/WEBHOOKS.md +653 -0
  138. package/handlers/app-definition-loader.js +67 -0
  139. package/handlers/app-handler-helpers.js +3 -3
  140. package/handlers/backend-utils.js +271 -42
  141. package/handlers/database-migration-handler.js +227 -0
  142. package/handlers/integration-event-dispatcher.js +68 -0
  143. package/handlers/middleware/admin-auth.js +73 -0
  144. package/handlers/routers/HEALTHCHECK.md +342 -0
  145. package/handlers/routers/auth.js +4 -15
  146. package/handlers/routers/db-migration.handler.js +29 -0
  147. package/handlers/routers/db-migration.js +329 -0
  148. package/handlers/routers/health.js +518 -0
  149. package/handlers/routers/integration-defined-routers.js +85 -10
  150. package/handlers/routers/integration-webhook-routers.js +80 -0
  151. package/handlers/routers/user.js +27 -5
  152. package/handlers/routers/websocket.js +5 -3
  153. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  154. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  155. package/handlers/workers/db-migration.js +427 -0
  156. package/handlers/workers/dlq-processor.js +63 -0
  157. package/handlers/workers/integration-defined-workers.js +13 -7
  158. package/index.js +102 -46
  159. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  160. package/infrastructure/scheduler/index.js +33 -0
  161. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  162. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  163. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  164. package/integrations/EXTENSIONS.md +240 -0
  165. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  166. package/integrations/extension.js +254 -0
  167. package/integrations/index.js +20 -10
  168. package/integrations/integration-base.js +730 -74
  169. package/integrations/integration-router.js +398 -180
  170. package/integrations/options.js +1 -1
  171. package/integrations/repositories/config-patch-shared.js +43 -0
  172. package/integrations/repositories/integration-mapping-repository-documentdb.js +303 -0
  173. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  174. package/integrations/repositories/integration-mapping-repository-interface.js +120 -0
  175. package/integrations/repositories/integration-mapping-repository-mongo.js +183 -0
  176. package/integrations/repositories/integration-mapping-repository-postgres.js +255 -0
  177. package/integrations/repositories/integration-mapping-repository.js +156 -0
  178. package/integrations/repositories/integration-repository-documentdb.js +322 -0
  179. package/integrations/repositories/integration-repository-factory.js +51 -0
  180. package/integrations/repositories/integration-repository-interface.js +189 -0
  181. package/integrations/repositories/integration-repository-mongo.js +450 -0
  182. package/integrations/repositories/integration-repository-postgres.js +492 -0
  183. package/integrations/repositories/process-repository-documentdb.js +311 -0
  184. package/integrations/repositories/process-repository-factory.js +53 -0
  185. package/integrations/repositories/process-repository-interface.js +136 -0
  186. package/integrations/repositories/process-repository-mongo.js +262 -0
  187. package/integrations/repositories/process-repository-postgres.js +380 -0
  188. package/integrations/repositories/process-update-ops-shared.js +112 -0
  189. package/integrations/repositories/report-id.js +13 -0
  190. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  191. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  192. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  193. package/integrations/use-cases/create-integration.js +215 -0
  194. package/integrations/use-cases/create-process.js +130 -0
  195. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  196. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  197. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  198. package/integrations/use-cases/get-integration-for-user.js +78 -0
  199. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  200. package/integrations/use-cases/get-integration-instance.js +83 -0
  201. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  202. package/integrations/use-cases/get-possible-integrations.js +27 -0
  203. package/integrations/use-cases/get-process.js +89 -0
  204. package/integrations/use-cases/index.js +19 -0
  205. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  206. package/integrations/use-cases/load-integration-context.js +71 -0
  207. package/integrations/use-cases/patch-integration-config.js +39 -0
  208. package/integrations/use-cases/process-errors.js +28 -0
  209. package/integrations/use-cases/update-integration-config.js +32 -0
  210. package/integrations/use-cases/update-integration-messages.js +44 -0
  211. package/integrations/use-cases/update-integration-status.js +32 -0
  212. package/integrations/use-cases/update-integration.js +92 -0
  213. package/integrations/use-cases/update-process-metrics.js +222 -0
  214. package/integrations/use-cases/update-process-state.js +163 -0
  215. package/integrations/utils/map-integration-dto.js +37 -0
  216. package/jest-global-setup-noop.js +3 -0
  217. package/jest-global-teardown-noop.js +3 -0
  218. package/logs/logger.js +0 -4
  219. package/{module-plugin → modules}/index.js +0 -10
  220. package/modules/module-factory.js +56 -0
  221. package/modules/module.js +307 -0
  222. package/modules/repositories/module-repository-documentdb.js +350 -0
  223. package/modules/repositories/module-repository-factory.js +40 -0
  224. package/modules/repositories/module-repository-interface.js +145 -0
  225. package/modules/repositories/module-repository-mongo.js +436 -0
  226. package/modules/repositories/module-repository-postgres.js +481 -0
  227. package/modules/repositories/module-repository.js +369 -0
  228. package/modules/requester/api-key.js +52 -0
  229. package/modules/requester/oauth-2.js +555 -0
  230. package/modules/requester/requester.js +542 -0
  231. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  232. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  233. package/modules/tests/doubles/test-module-factory.js +16 -0
  234. package/modules/tests/doubles/test-module-repository.js +39 -0
  235. package/modules/use-cases/get-entities-for-user.js +32 -0
  236. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  237. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  238. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  239. package/modules/use-cases/get-module.js +74 -0
  240. package/modules/use-cases/process-authorization-callback.js +243 -0
  241. package/modules/use-cases/refresh-entity-options.js +72 -0
  242. package/modules/use-cases/test-module-auth.js +72 -0
  243. package/modules/utils/map-module-dto.js +18 -0
  244. package/package.json +92 -50
  245. package/prisma-mongodb/schema.prisma +455 -0
  246. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  247. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  248. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  249. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  250. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  251. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  252. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  253. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  254. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  255. package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
  256. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  257. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  258. package/prisma-postgresql/schema.prisma +436 -0
  259. package/queues/queuer-util.js +103 -21
  260. package/reporting/README.md +154 -0
  261. package/reporting/builtin-reports.js +6 -0
  262. package/reporting/index.js +13 -0
  263. package/reporting/report-base.js +49 -0
  264. package/reporting/reports/integrations-report.js +221 -0
  265. package/syncs/manager.js +468 -443
  266. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  267. package/syncs/repositories/sync-repository-factory.js +43 -0
  268. package/syncs/repositories/sync-repository-interface.js +109 -0
  269. package/syncs/repositories/sync-repository-mongo.js +239 -0
  270. package/syncs/repositories/sync-repository-postgres.js +319 -0
  271. package/syncs/sync.js +0 -1
  272. package/telemetry/README.md +331 -0
  273. package/telemetry/bind-telemetry-context.js +73 -0
  274. package/telemetry/canonical-counters.js +52 -0
  275. package/telemetry/exporters.js +85 -0
  276. package/telemetry/index.js +26 -0
  277. package/telemetry/instrument-handler.js +87 -0
  278. package/telemetry/no-op-telemetry.js +67 -0
  279. package/telemetry/north-star.js +103 -0
  280. package/telemetry/otel-telemetry.js +213 -0
  281. package/telemetry/plugin-subscribers.js +77 -0
  282. package/telemetry/telemetry-config.js +120 -0
  283. package/telemetry/telemetry-context.js +40 -0
  284. package/telemetry/telemetry-event-bus.js +58 -0
  285. package/telemetry/telemetry-runtime.js +147 -0
  286. package/telemetry/telemetry-service.js +51 -0
  287. package/telemetry/usage-rollup-subscriber.js +116 -0
  288. package/token/repositories/token-repository-documentdb.js +137 -0
  289. package/token/repositories/token-repository-factory.js +40 -0
  290. package/token/repositories/token-repository-interface.js +131 -0
  291. package/token/repositories/token-repository-mongo.js +219 -0
  292. package/token/repositories/token-repository-postgres.js +264 -0
  293. package/token/repositories/token-repository.js +219 -0
  294. package/types/associations/index.d.ts +0 -17
  295. package/types/core/index.d.ts +12 -4
  296. package/types/database/index.d.ts +10 -2
  297. package/types/encrypt/index.d.ts +5 -3
  298. package/types/integrations/index.d.ts +3 -8
  299. package/types/module-plugin/index.d.ts +20 -69
  300. package/types/syncs/index.d.ts +0 -17
  301. package/usage/README.md +54 -0
  302. package/usage/index.js +17 -0
  303. package/usage/repositories/usage-repository-documentdb.js +194 -0
  304. package/usage/repositories/usage-repository-factory.js +25 -0
  305. package/usage/repositories/usage-repository-interface.js +37 -0
  306. package/usage/repositories/usage-repository-prisma.js +146 -0
  307. package/usage/tracked-metrics.js +38 -0
  308. package/usage/usage-windows.js +24 -0
  309. package/user/repositories/user-repository-documentdb.js +458 -0
  310. package/user/repositories/user-repository-factory.js +52 -0
  311. package/user/repositories/user-repository-interface.js +214 -0
  312. package/user/repositories/user-repository-mongo.js +323 -0
  313. package/user/repositories/user-repository-postgres.js +377 -0
  314. package/user/tests/doubles/test-user-repository.js +72 -0
  315. package/user/use-cases/authenticate-user.js +127 -0
  316. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  317. package/user/use-cases/create-individual-user.js +61 -0
  318. package/user/use-cases/create-organization-user.js +47 -0
  319. package/user/use-cases/create-token-for-user-id.js +30 -0
  320. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  321. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  322. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  323. package/user/use-cases/login-user.js +122 -0
  324. package/user/user.js +125 -0
  325. package/utils/backend-path.js +38 -0
  326. package/utils/index.js +6 -0
  327. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  328. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  329. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  330. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  331. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  332. package/websocket/repositories/websocket-connection-repository.js +161 -0
  333. package/assertions/is-equal.js +0 -17
  334. package/associations/model.js +0 -54
  335. package/database/models/IndividualUser.js +0 -76
  336. package/database/models/OrganizationUser.js +0 -29
  337. package/database/models/State.js +0 -9
  338. package/database/models/Token.js +0 -70
  339. package/database/models/UserModel.js +0 -7
  340. package/database/models/WebsocketConnection.js +0 -49
  341. package/database/mongo.js +0 -45
  342. package/database/mongoose.js +0 -5
  343. package/encrypt/Cryptor.test.js +0 -32
  344. package/encrypt/encrypt.js +0 -132
  345. package/encrypt/encrypt.test.js +0 -1069
  346. package/encrypt/test-encrypt.js +0 -107
  347. package/errors/base-error.test.js +0 -32
  348. package/errors/fetch-error.test.js +0 -79
  349. package/errors/halt-error.test.js +0 -11
  350. package/errors/validation-errors.test.js +0 -120
  351. package/handlers/routers/middleware/loadUser.js +0 -15
  352. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  353. package/integrations/create-frigg-backend.js +0 -31
  354. package/integrations/integration-factory.js +0 -251
  355. package/integrations/integration-mapping.js +0 -43
  356. package/integrations/integration-model.js +0 -46
  357. package/integrations/integration-user.js +0 -144
  358. package/integrations/test/integration-base.test.js +0 -144
  359. package/lambda/TimeoutCatcher.test.js +0 -68
  360. package/logs/logger.test.js +0 -76
  361. package/module-plugin/auther.js +0 -393
  362. package/module-plugin/credential.js +0 -22
  363. package/module-plugin/entity-manager.js +0 -70
  364. package/module-plugin/entity.js +0 -46
  365. package/module-plugin/manager.js +0 -169
  366. package/module-plugin/module-factory.js +0 -61
  367. package/module-plugin/requester/api-key.js +0 -36
  368. package/module-plugin/requester/oauth-2.js +0 -219
  369. package/module-plugin/requester/requester.js +0 -165
  370. package/module-plugin/requester/requester.test.js +0 -28
  371. package/module-plugin/test/auther.test.js +0 -97
  372. package/syncs/model.js +0 -62
  373. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  374. /package/{module-plugin → modules}/requester/basic.js +0 -0
  375. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,75 +1,26 @@
1
1
  declare module "@friggframework/module-plugin" {
2
- import { Model } from "mongoose";
3
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
4
3
 
5
- export class Credential extends Model {
6
- userId: string;
7
- auth_is_valid: boolean;
8
- subType: string;
9
- externalId: string;
4
+ export interface Credential {
5
+ id?: string;
6
+ userId?: string;
7
+ authIsValid?: boolean;
8
+ externalId?: string;
9
+ data?: any;
10
10
  }
11
11
 
12
- export class EntityManager implements IFriggEntityManager {
13
- static primaryEntityClass: any;
14
- static entityManagerClasses: any[];
15
- static entityTypes: string[];
16
- static getEntitiesForUser(userId: string): Promise<any[]>;
17
- static checkIsValidType(entityType: string): boolean;
18
- static getEntityManagerClass(entityType?: string): any;
19
-
20
- static getEntityManagerInstanceFromEntityId(
21
- entityId: string,
22
- userId: string
23
- ): Promise<any>;
24
- }
25
-
26
- interface IFriggEntityManager {}
27
-
28
- export class Entity extends Model {
29
- credentialId: string;
30
- subType: string;
31
- userId: string;
32
- name: string;
33
- externalId: string;
12
+ export interface Entity {
13
+ id?: string;
14
+ credentialId?: string;
15
+ userId?: string;
16
+ name?: string;
17
+ moduleName?: string;
18
+ externalId?: string;
19
+ data?: any;
34
20
  }
35
21
 
36
22
  export type MappedEntity = Entity & { id: string; type: any };
37
- export class ModuleManager extends Delegate implements IFriggModuleManager {
38
- static Entity: Entity;
39
- static Credential: Credential;
40
23
 
41
- constructor(params: { userId: string });
42
-
43
- static getName(): any;
44
- static getInstance(params: any): Promise<any>;
45
- static getEntitiesForUserId(userId: string): Promise<MappedEntity[]>;
46
-
47
- batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
48
- batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
49
- findOrCreateEntity(params: any): Promise<any>;
50
- getAllSyncObjects(SyncClass: any): Promise<any>;
51
- getAuthorizationRequirements(params: any): Promise<any>;
52
- getEntityId(): Promise<string>;
53
- getEntityOptions(): Promise<any>;
54
- markCredentialsInvalid(): Promise<Credential>;
55
- processAuthorizationCallback(params: any): Promise<any>;
56
- testAuth(params: any): Promise<any>;
57
- validateAuthorizationRequirements(): Promise<boolean>;
58
- }
59
-
60
- interface IFriggModuleManager extends IFriggDelegate {
61
- getEntityId(): Promise<string>;
62
- validateAuthorizationRequirements(): Promise<boolean>;
63
- getAuthorizationRequirements(params: any): Promise<any>;
64
- testAuth(params: any): Promise<any>;
65
- processAuthorizationCallback(params: any): Promise<any>;
66
- getEntityOptions(): Promise<any>;
67
- findOrCreateEntity(params: any): Promise<any>;
68
- getAllSyncObjects(SyncClass: any): Promise<any>;
69
- batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
70
- batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
71
- markCredentialsInvalid(): Promise<Credential>;
72
- }
73
24
 
74
25
  export class Requester implements IFriggRequester {
75
26
  DLGT_INVALID_AUTH: string;
@@ -90,6 +41,7 @@ declare module "@friggframework/module-plugin" {
90
41
  ): Promise<any>;
91
42
  parseBody(response: any): Promise<any>;
92
43
  refreshAuth(): Promise<any>;
44
+ _adoptNewerCredential(): Promise<boolean>;
93
45
 
94
46
  delegate: any;
95
47
  delegateTypes: any[];
@@ -121,6 +73,7 @@ declare module "@friggframework/module-plugin" {
121
73
  _put(options: RequestOptions): Promise<any>;
122
74
  _delete(options: RequestOptions): Promise<any>;
123
75
  refreshAuth(): Promise<any>;
76
+ _adoptNewerCredential(): Promise<boolean>;
124
77
  }
125
78
 
126
79
  type RequestOptions = {
@@ -138,8 +91,7 @@ declare module "@friggframework/module-plugin" {
138
91
 
139
92
  export class ApiKeyRequester
140
93
  extends Requester
141
- implements IFriggApiKeyRequester
142
- {
94
+ implements IFriggApiKeyRequester {
143
95
  API_KEY_NAME: string;
144
96
  API_KEY_VALUE: any;
145
97
 
@@ -160,8 +112,7 @@ declare module "@friggframework/module-plugin" {
160
112
 
161
113
  export class BasicAuthRequester
162
114
  extends Requester
163
- implements IFriggBasicAuthRequester
164
- {
115
+ implements IFriggBasicAuthRequester {
165
116
  password: string;
166
117
  username: string;
167
118
 
@@ -189,8 +140,7 @@ declare module "@friggframework/module-plugin" {
189
140
 
190
141
  export class OAuth2Requester
191
142
  extends Requester
192
- implements IFriggOAuth2Requester
193
- {
143
+ implements IFriggOAuth2Requester {
194
144
  DLGT_TOKEN_DEAUTHORIZED: string;
195
145
  DLGT_TOKEN_UPDATE: string;
196
146
  accessTokenExpire: any;
@@ -253,6 +203,7 @@ declare module "@friggframework/module-plugin" {
253
203
  addAuthHeaders(headers: object): Promise<object>;
254
204
  isAuthenticated(): boolean;
255
205
  refreshAuth(): Promise<void>;
206
+ _adoptNewerCredential(): Promise<boolean>;
256
207
  getTokenFromUsernamePassword(): Promise<Token>;
257
208
  getTokenFromClientCredentials(): Promise<Token>;
258
209
  }
@@ -1,18 +1,3 @@
1
- declare module "@friggframework/syncs/model" {
2
- import { Model } from "mongoose";
3
-
4
- export class Sync extends Model {
5
- entities: any[];
6
- hash: string;
7
- name: string;
8
- dataIdentifiers: {
9
- entity: any;
10
- id: object;
11
- hash: string;
12
- }[];
13
- }
14
- }
15
-
16
1
  declare module "@friggframework/syncs/manager" {
17
2
  import Sync from "@friggframework/syncs/sync";
18
3
 
@@ -28,7 +13,6 @@ declare module "@friggframework/syncs/manager" {
28
13
  confirmCreate(
29
14
  syncObj: Sync,
30
15
  createdId: string,
31
- moduleManager: any
32
16
  ): Promise<any>;
33
17
  confirmUpdate(syncObj: Sync): Promise<any>;
34
18
  createSyncDBObject(objArr: any[], entities: any[]): Promise<any>;
@@ -50,7 +34,6 @@ declare module "@friggframework/syncs/manager" {
50
34
  confirmCreate(
51
35
  syncObj: Sync,
52
36
  createdId: string,
53
- moduleManager: any
54
37
  ): Promise<any>;
55
38
  confirmUpdate(syncObj: Sync): Promise<any>;
56
39
  }
@@ -0,0 +1,54 @@
1
+ # Usage Store
2
+
3
+ The durable, Frigg-owned store behind ADR-011 feature-usage tracking. Holds
4
+ per-integration counters that the reporting endpoint reads for cross-integration
5
+ comparison and trend series.
6
+
7
+ Populated by the telemetry usage rollup and read via `frigg.usage.*` — see
8
+ [`telemetry/README.md`](../telemetry/README.md) for the full guide, config, and
9
+ usage examples. This README covers the store internals only.
10
+
11
+ ## Fact row
12
+
13
+ `UsageCounter { integrationId, integrationType, metric, window, value, updatedAt }`,
14
+ uniquely keyed by `(integrationId, integrationType, metric, window)`. `window` is
15
+ `day:YYYY-MM-DD` or `hour:YYYY-MM-DDTHH` (UTC).
16
+
17
+ ## Isolation (ADR-010 Decision 3)
18
+
19
+ The store is deliberately isolated from user/integration-scoped data:
20
+
21
+ - **No `userId`** and **no foreign key** to `Integration` — a user-scoped query
22
+ can never return a usage row, and usage history survives integration deletion.
23
+ - **Not** in the encryption registry — dimensions are bounded and non-sensitive.
24
+
25
+ ## Repository triad
26
+
27
+ Mirrors the reporting/process pattern — an interface plus PostgreSQL, MongoDB and
28
+ DocumentDB adapters selected by `DB_TYPE`:
29
+
30
+ ```js
31
+ const { createUsageRepository } = require('@friggframework/core'); // usage-repository-factory
32
+
33
+ class UsageRepositoryInterface {
34
+ async increment({ integrationId, integrationType, metric, window, value }) {} // atomic upsert
35
+ async getTotalsByDimension({ metric, groupBy, since, bucket }) {} // comparison (one window granularity)
36
+ async getTimeSeries({ metric, integrationType, from, to, bucket }) {} // trend (aggregated across instances)
37
+ }
38
+ ```
39
+
40
+ - **`increment`** is atomic: PostgreSQL uses Prisma `upsert` with
41
+ `{ value: { increment } }`; a concurrent first-insert race (`P2002`) retries
42
+ once onto the atomic update path.
43
+ - **`getTotalsByDimension`** filters to a single window granularity (default `day`) so day and
44
+ hour rows are never double-summed. Requires a `metric`; `groupBy` is
45
+ allow-listed to `integrationType` / `metric`.
46
+ - **`getTimeSeries`** aggregates across integration instances (`groupBy(window) + sum`)
47
+ and range-filters on the window key. Requires an `integrationType`.
48
+
49
+ > **DocumentDB:** the adapter overrides increment/getTotalsByDimension/getTimeSeries with raw commands
50
+ > (`$runCommandRaw`: a `$inc` upsert via `documentdb-utils.updateOne`, and a
51
+ > cursor-drained `$aggregate` `$group/$sum`) — matching every other DocumentDB
52
+ > adapter, since Prisma's Mongo engine emits upsert/groupBy shapes DocumentDB
53
+ > rejects and cursor reads truncate at ~101 docs. Command shapes are unit-tested;
54
+ > run an end-to-end check against a real cluster before GA.
package/usage/index.js ADDED
@@ -0,0 +1,17 @@
1
+ const {
2
+ createUsageRepository,
3
+ UsageRepositoryPrisma,
4
+ UsageRepositoryDocumentDB,
5
+ } = require('./repositories/usage-repository-factory');
6
+ const {
7
+ UsageRepositoryInterface,
8
+ } = require('./repositories/usage-repository-interface');
9
+ const { computeTrackedMetrics } = require('./tracked-metrics');
10
+
11
+ module.exports = {
12
+ createUsageRepository,
13
+ computeTrackedMetrics,
14
+ UsageRepositoryInterface,
15
+ UsageRepositoryPrisma,
16
+ UsageRepositoryDocumentDB,
17
+ };
@@ -0,0 +1,194 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { updateOne } = require('../../database/documentdb-utils');
3
+ const { UsageRepositoryPrisma } = require('./usage-repository-prisma');
4
+ const { windowKey } = require('../usage-windows');
5
+
6
+ const COLLECTION = 'UsageCounter';
7
+ const DRAIN_BATCH_SIZE = 1000;
8
+ const MAX_BATCHES = 100000;
9
+ const VALID_GROUP_BY = new Set(['integrationType', 'metric']);
10
+ const VALID_BUCKETS = new Set(['day', 'hour']);
11
+
12
+ /**
13
+ * DocumentDB usage store. Amazon DocumentDB does not accept the command shapes
14
+ * Prisma's Mongo engine emits for `upsert({ update: { value: { increment } } })`
15
+ * and `groupBy({ _sum })`, and cursor reads truncate at ~101 docs — so, like
16
+ * every other DocumentDB adapter in this repo, these operations are issued as
17
+ * raw commands (`$runCommandRaw`) via the validated documentdb-utils helpers and
18
+ * a drained aggregate cursor. No timestamps are managed: getTotalsByDimension/getTimeSeries filter on
19
+ * the window KEY (not write-time), so createdAt/updatedAt are unnecessary here.
20
+ */
21
+ class UsageRepositoryDocumentDB extends UsageRepositoryPrisma {
22
+ constructor() {
23
+ super();
24
+ this.prisma = prisma;
25
+ }
26
+
27
+ async increment({ integrationId, integrationType, metric, window, value = 1 }) {
28
+ // Atomic upsert-increment: $inc creates the field at the increment value
29
+ // on insert; $setOnInsert stamps the identity on first write. The compound
30
+ // filter is the unique key, so concurrent writers converge on one row.
31
+ await updateOne(
32
+ this.prisma,
33
+ COLLECTION,
34
+ { integrationId, integrationType, metric, window },
35
+ {
36
+ $inc: { value },
37
+ $setOnInsert: {
38
+ integrationId,
39
+ integrationType,
40
+ metric,
41
+ window,
42
+ },
43
+ },
44
+ { upsert: true }
45
+ );
46
+ }
47
+
48
+ async getTotalsByDimension({
49
+ metric,
50
+ groupBy = 'integrationType',
51
+ since,
52
+ bucket = 'day',
53
+ } = {}) {
54
+ if (!metric) {
55
+ throw new Error('getTotalsByDimension requires a metric (units are per-metric)');
56
+ }
57
+ assertGroupBy(groupBy);
58
+ assertBucket(bucket);
59
+
60
+ // Single window granularity (day: OR hour:) — never sum across both.
61
+ // `since` bounds on the window KEY (mirrors getTimeSeries / the Prisma adapter).
62
+ const rows = await this._aggregateDrained([
63
+ {
64
+ $match: {
65
+ metric,
66
+ window: windowMatch(bucket, {
67
+ gte: since ? windowKey(bucket, since) : undefined,
68
+ }),
69
+ },
70
+ },
71
+ { $group: { _id: `$${groupBy}`, value: { $sum: '$value' } } },
72
+ ]);
73
+
74
+ return rows.map((row) => ({
75
+ [groupBy]: row._id,
76
+ value: toNumber(row.value),
77
+ }));
78
+ }
79
+
80
+ async getTimeSeries({ metric, integrationType, from, to, bucket = 'day' } = {}) {
81
+ assertBucket(bucket);
82
+ if (!integrationType) {
83
+ throw new Error('getTimeSeries requires an integrationType');
84
+ }
85
+
86
+ const rows = await this._aggregateDrained([
87
+ {
88
+ $match: {
89
+ metric,
90
+ integrationType,
91
+ window: windowMatch(bucket, {
92
+ gte: from ? windowKey(bucket, from) : undefined,
93
+ lte: to ? windowKey(bucket, to) : undefined,
94
+ }),
95
+ },
96
+ },
97
+ { $group: { _id: '$window', value: { $sum: '$value' } } },
98
+ { $sort: { _id: 1 } },
99
+ ]);
100
+
101
+ return rows.map((row) => ({
102
+ bucket: row._id,
103
+ value: toNumber(row.value),
104
+ }));
105
+ }
106
+
107
+ async _aggregateDrained(pipeline) {
108
+ const first = await this.prisma.$runCommandRaw({
109
+ aggregate: COLLECTION,
110
+ pipeline,
111
+ cursor: { batchSize: DRAIN_BATCH_SIZE },
112
+ });
113
+ return drainCursor(this.prisma, COLLECTION, first);
114
+ }
115
+ }
116
+
117
+ /** A `$match` window clause: prefix by granularity, optionally range-bounded. */
118
+ function windowMatch(bucket, { gte, lte } = {}) {
119
+ const clause = { $regex: `^${bucket}:` };
120
+ if (gte) clause.$gte = gte;
121
+ if (lte) clause.$lte = lte;
122
+ return clause;
123
+ }
124
+
125
+ /**
126
+ * Coerce an aggregate `$sum` result to a JS Number. Prisma $runCommandRaw returns
127
+ * extended JSON, so a 64-bit sum can arrive as { $numberLong: "..." } (or
128
+ * $numberInt/$numberDouble); counts never approach 2^53.
129
+ */
130
+ function toNumber(value) {
131
+ if (value === null || value === undefined) return 0;
132
+ if (typeof value === 'number') return value;
133
+ if (typeof value === 'bigint') return Number(value);
134
+ if (typeof value === 'object') {
135
+ const raw =
136
+ value.$numberLong ?? value.$numberInt ?? value.$numberDouble;
137
+ if (raw !== undefined) return Number(raw);
138
+ }
139
+ return Number(value) || 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_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
+ function isCursorOpen(id) {
165
+ if (id === undefined || id === null) return false;
166
+ if (typeof id === 'number') return id !== 0;
167
+ if (typeof id === 'bigint') return id !== 0n;
168
+ if (typeof id === 'object' && id.$numberLong !== undefined) {
169
+ return id.$numberLong !== '0';
170
+ }
171
+ return String(id) !== '0';
172
+ }
173
+
174
+ function assertGroupBy(groupBy) {
175
+ if (!VALID_GROUP_BY.has(groupBy)) {
176
+ throw new Error(
177
+ `Invalid groupBy "${groupBy}". Allowed: ${[...VALID_GROUP_BY].join(
178
+ ', '
179
+ )}`
180
+ );
181
+ }
182
+ }
183
+
184
+ function assertBucket(bucket) {
185
+ if (!VALID_BUCKETS.has(bucket)) {
186
+ throw new Error(
187
+ `Invalid bucket "${bucket}". Allowed: ${[...VALID_BUCKETS].join(
188
+ ', '
189
+ )}`
190
+ );
191
+ }
192
+ }
193
+
194
+ module.exports = { UsageRepositoryDocumentDB };
@@ -0,0 +1,25 @@
1
+ const { UsageRepositoryPrisma } = require('./usage-repository-prisma');
2
+ const { UsageRepositoryDocumentDB } = require('./usage-repository-documentdb');
3
+ const config = require('../../database/config');
4
+
5
+ function createUsageRepository() {
6
+ const dbType = config.DB_TYPE;
7
+
8
+ switch (dbType) {
9
+ case 'mongodb':
10
+ case 'postgresql':
11
+ return new UsageRepositoryPrisma();
12
+ case 'documentdb':
13
+ return new UsageRepositoryDocumentDB();
14
+ default:
15
+ throw new Error(
16
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
17
+ );
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ createUsageRepository,
23
+ UsageRepositoryPrisma,
24
+ UsageRepositoryDocumentDB,
25
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Port for the durable usage-counter store.
3
+ *
4
+ * The store is deliberately isolated (ADR-010 Decision 3): its fact rows carry
5
+ * NO userId and NO foreign key to Integration, so a user-scoped query can never
6
+ * return a usage row and usage history survives integration deletion.
7
+ *
8
+ * Fact row: { integrationId, integrationType, metric, window, value, updatedAt }
9
+ * Dimensions must be bounded — high-cardinality ids stay on traces.
10
+ */
11
+ class UsageRepositoryInterface {
12
+ /**
13
+ * Atomically add `value` to the counter for
14
+ * (integrationId, integrationType, metric, window), inserting if absent.
15
+ */
16
+ async increment(/* { integrationId, integrationType, metric, window, value } */) {
17
+ throw new Error('increment must be implemented by subclass');
18
+ }
19
+
20
+ /**
21
+ * Sum a metric grouped by a bounded dimension since an optional timestamp.
22
+ * @returns {Promise<Array<{[groupBy]: string, value: number}>>}
23
+ */
24
+ async getTotalsByDimension(/* { metric, groupBy, since } */) {
25
+ throw new Error('getTotalsByDimension must be implemented by subclass');
26
+ }
27
+
28
+ /**
29
+ * Time series of a metric for one integration type.
30
+ * @returns {Promise<Array<{bucket: string, value: number}>>}
31
+ */
32
+ async getTimeSeries(/* { metric, integrationType, from, to, bucket } */) {
33
+ throw new Error('getTimeSeries must be implemented by subclass');
34
+ }
35
+ }
36
+
37
+ module.exports = { UsageRepositoryInterface };
@@ -0,0 +1,146 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { UsageRepositoryInterface } = require('./usage-repository-interface');
3
+ const { windowKey } = require('../usage-windows');
4
+
5
+ /**
6
+ * Prisma-backed usage store — the single implementation for both mongodb and
7
+ * postgresql (Prisma abstracts the store; integrationId is a plain string in
8
+ * both). DocumentDB extends this but overrides the write/read ops with raw
9
+ * commands. All queries touch only the isolated `UsageCounter` model — never
10
+ * user/integration-scoped tables.
11
+ */
12
+ class UsageRepositoryPrisma extends UsageRepositoryInterface {
13
+ constructor() {
14
+ super();
15
+ this.prisma = prisma;
16
+ }
17
+
18
+ async increment({
19
+ integrationId,
20
+ integrationType,
21
+ metric,
22
+ window,
23
+ value = 1,
24
+ }) {
25
+ const where = {
26
+ integrationId_integrationType_metric_window: {
27
+ integrationId,
28
+ integrationType,
29
+ metric,
30
+ window,
31
+ },
32
+ };
33
+ const upsertArgs = {
34
+ where,
35
+ create: { integrationId, integrationType, metric, window, value },
36
+ update: { value: { increment: value } },
37
+ };
38
+
39
+ // Concurrent first-insert race: two workers both INSERT and one hits the
40
+ // unique constraint (P2002). After the winner's insert the row exists, so
41
+ // a retry takes the atomic UPDATE (increment) path. Bounded so a
42
+ // pathological repeated race can't throw unexpectedly out of the public
43
+ // `recordUsageCounter` write; non-conflict errors surface immediately.
44
+ for (let attempt = 1; ; attempt++) {
45
+ try {
46
+ await this.prisma.usageCounter.upsert(upsertArgs);
47
+ return;
48
+ } catch (err) {
49
+ if (err && err.code === 'P2002' && attempt < MAX_UPSERT_ATTEMPTS) {
50
+ continue;
51
+ }
52
+ throw err;
53
+ }
54
+ }
55
+ }
56
+
57
+ async getTotalsByDimension({
58
+ metric,
59
+ groupBy = 'integrationType',
60
+ since,
61
+ bucket = 'day',
62
+ } = {}) {
63
+ if (!metric) {
64
+ throw new Error('getTotalsByDimension requires a metric (units are per-metric)');
65
+ }
66
+ assertGroupBy(groupBy);
67
+ assertBucket(bucket);
68
+
69
+ // Filter to ONE window granularity — every event is written to both a
70
+ // day: and an hour: row, so summing across granularities would double
71
+ // (or worse) the true count.
72
+ // Bound `since` on the WINDOW key (mirrors getTimeSeries) — write-time
73
+ // updatedAt would misplace a late/redelivered increment for an earlier
74
+ // window, over- or under-counting the time-bounded total.
75
+ const where = { metric, window: { startsWith: `${bucket}:` } };
76
+ if (since) where.window.gte = windowKey(bucket, since);
77
+
78
+ const groups = await this.prisma.usageCounter.groupBy({
79
+ by: [groupBy],
80
+ where,
81
+ _sum: { value: true },
82
+ });
83
+
84
+ // value is a BigInt column — coerce the sum to a JSON-safe Number
85
+ // (JSON.stringify throws on BigInt; counts never approach 2^53).
86
+ return groups.map((group) => ({
87
+ [groupBy]: group[groupBy],
88
+ value: Number(group._sum?.value ?? 0),
89
+ }));
90
+ }
91
+
92
+ async getTimeSeries({ metric, integrationType, from, to, bucket = 'day' } = {}) {
93
+ assertBucket(bucket);
94
+ if (!integrationType) {
95
+ throw new Error('getTimeSeries requires an integrationType');
96
+ }
97
+
98
+ // Range-filter on the WINDOW key (write-time `updatedAt` would misplace a
99
+ // late increment for an earlier window). Window keys are ISO-lexicographic
100
+ // within a granularity, so string gte/lte gives the correct range.
101
+ const window = { startsWith: `${bucket}:` };
102
+ if (from) window.gte = windowKey(bucket, from);
103
+ if (to) window.lte = windowKey(bucket, to);
104
+
105
+ // Aggregate ACROSS integration instances: there is one row per
106
+ // (integrationId, integrationType, metric, window), so a type with many
107
+ // instances has many rows per window — sum them into one point.
108
+ const groups = await this.prisma.usageCounter.groupBy({
109
+ by: ['window'],
110
+ where: { metric, integrationType, window },
111
+ _sum: { value: true },
112
+ orderBy: { window: 'asc' },
113
+ });
114
+
115
+ return groups.map((group) => ({
116
+ bucket: group.window,
117
+ value: Number(group._sum?.value ?? 0),
118
+ }));
119
+ }
120
+ }
121
+
122
+ const MAX_UPSERT_ATTEMPTS = 3;
123
+ const VALID_GROUP_BY = new Set(['integrationType', 'metric']);
124
+ const VALID_BUCKETS = new Set(['day', 'hour']);
125
+
126
+ function assertGroupBy(groupBy) {
127
+ if (!VALID_GROUP_BY.has(groupBy)) {
128
+ throw new Error(
129
+ `Invalid groupBy "${groupBy}". Allowed: ${[...VALID_GROUP_BY].join(
130
+ ', '
131
+ )}`
132
+ );
133
+ }
134
+ }
135
+
136
+ function assertBucket(bucket) {
137
+ if (!VALID_BUCKETS.has(bucket)) {
138
+ throw new Error(
139
+ `Invalid bucket "${bucket}". Allowed: ${[...VALID_BUCKETS].join(
140
+ ', '
141
+ )}`
142
+ );
143
+ }
144
+ }
145
+
146
+ module.exports = { UsageRepositoryPrisma };
@@ -0,0 +1,38 @@
1
+ const { isCanonicalCounter } = require('../telemetry/canonical-counters');
2
+
3
+ /**
4
+ * Compute the set of usage-counter keys the rollup should persist, from each
5
+ * integration's `Definition.usage` opt-in.
6
+ * Declaring a canonical key opts into cross-type comparison + the rollup; custom
7
+ * keys are tracked per integration type. Unknown canonical keys are dropped with
8
+ * a warning (lazy validation, matching how Definition is treated elsewhere).
9
+ */
10
+ function computeTrackedMetrics(integrationClasses = []) {
11
+ const tracked = new Set();
12
+ if (!Array.isArray(integrationClasses)) return tracked;
13
+
14
+ for (const IntegrationClass of integrationClasses) {
15
+ const usage = IntegrationClass?.Definition?.usage;
16
+ if (!usage) continue;
17
+ const name = IntegrationClass.Definition?.name || 'integration';
18
+
19
+ for (const key of usage.canonical || []) {
20
+ if (!isCanonicalCounter(key)) {
21
+ console.warn(
22
+ `[Frigg][usage] "${name}" declares unknown canonical counter "${key}" — ignored. ` +
23
+ `Use a key from CANONICAL_COUNTERS or declare it under usage.custom.`
24
+ );
25
+ continue;
26
+ }
27
+ tracked.add(key);
28
+ }
29
+
30
+ for (const key of Object.keys(usage.custom || {})) {
31
+ tracked.add(key);
32
+ }
33
+ }
34
+
35
+ return tracked;
36
+ }
37
+
38
+ module.exports = { computeTrackedMetrics };