@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
@@ -0,0 +1,213 @@
1
+ const otelApi = require('@opentelemetry/api');
2
+ const {
3
+ BasicTracerProvider,
4
+ BatchSpanProcessor,
5
+ ParentBasedSampler,
6
+ TraceIdRatioBasedSampler,
7
+ AlwaysOnSampler,
8
+ } = require('@opentelemetry/sdk-trace-base');
9
+ const {
10
+ MeterProvider,
11
+ PeriodicExportingMetricReader,
12
+ } = require('@opentelemetry/sdk-metrics');
13
+ const { resourceFromAttributes } = require('@opentelemetry/resources');
14
+ const { resolveExporter } = require('./exporters');
15
+ const { createTelemetryEventBus } = require('./telemetry-event-bus');
16
+ const {
17
+ runWithTelemetryContext,
18
+ mergeTelemetryContext,
19
+ } = require('./telemetry-context');
20
+
21
+ const TRACER_NAME = 'frigg';
22
+ const METRIC_EXPORT_INTERVAL_MS =
23
+ Number(process.env.OTEL_METRIC_EXPORT_INTERVAL_MS) || 60000;
24
+
25
+ /**
26
+ * Register an AsyncLocalStorage-backed OTel context manager once per process.
27
+ * Without it `BasicTracerProvider` uses the Noop context manager, so
28
+ * `startActiveSpan` never sets an active context — spans would be unparented
29
+ * roots, `getActiveSpan()` always undefined, and baggage inert. Guarded so
30
+ * repeated telemetry construction (tests) doesn't re-register.
31
+ */
32
+ let contextManagerRegistered = false;
33
+ function ensureContextManager() {
34
+ if (contextManagerRegistered) return;
35
+ contextManagerRegistered = true;
36
+ try {
37
+ const {
38
+ AsyncLocalStorageContextManager,
39
+ } = require('@opentelemetry/context-async-hooks');
40
+ const manager = new AsyncLocalStorageContextManager();
41
+ manager.enable();
42
+ otelApi.context.setGlobalContextManager(manager);
43
+ } catch (_) {
44
+ // If registration fails, traces are flat but usage attribution (which
45
+ // rides the separate telemetry-context ALS) is unaffected.
46
+ }
47
+ }
48
+
49
+ /**
50
+ * OpenTelemetry-backed telemetry adapter. Constructed only when a real exporter
51
+ * is configured (`createTelemetry` returns the no-op otherwise). `BatchSpanProcessor`
52
+ * is used uniformly — spans are delivered by the bounded `forceFlush()` the Lambda
53
+ * handler awaits before the container freezes (`callbackWaitsForEmptyEventLoop=false`),
54
+ * never by background timers.
55
+ */
56
+ class OtelTelemetry {
57
+ constructor({
58
+ exporter,
59
+ resource = {},
60
+ sampleRatio,
61
+ bus = createTelemetryEventBus(),
62
+ } = {}) {
63
+ ensureContextManager();
64
+ const { traceExporter, metricExporter } = resolveExporter(exporter);
65
+
66
+ const resourceAttrs = { 'service.name': resource.service || 'frigg' };
67
+ if (resource.stage) {
68
+ resourceAttrs['deployment.environment'] = resource.stage;
69
+ resourceAttrs.stage = resource.stage;
70
+ }
71
+ if (resource.appName) resourceAttrs.appName = resource.appName;
72
+ const res = resourceFromAttributes(resourceAttrs);
73
+
74
+ const sampler =
75
+ typeof sampleRatio === 'number' && sampleRatio < 1
76
+ ? new ParentBasedSampler({
77
+ root: new TraceIdRatioBasedSampler(sampleRatio),
78
+ })
79
+ : new AlwaysOnSampler();
80
+
81
+ this._tracerProvider = new BasicTracerProvider({
82
+ resource: res,
83
+ sampler,
84
+ spanProcessors: traceExporter
85
+ ? [new BatchSpanProcessor(traceExporter)]
86
+ : [],
87
+ });
88
+
89
+ const metricReader = metricExporter
90
+ ? new PeriodicExportingMetricReader({
91
+ exporter: metricExporter,
92
+ exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS,
93
+ })
94
+ : null;
95
+ this._meterProvider = new MeterProvider({
96
+ resource: res,
97
+ readers: metricReader ? [metricReader] : [],
98
+ });
99
+
100
+ this._bus = bus;
101
+ this._tracer = this._tracerProvider.getTracer(TRACER_NAME);
102
+ this._meter = this._meterProvider.getMeter(TRACER_NAME);
103
+ // One instrument instance per metric name — OTel requires it, and
104
+ // re-creating a counter would drop data points.
105
+ this._counters = new Map();
106
+ }
107
+
108
+ _getCounter(name) {
109
+ if (!this._counters.has(name)) {
110
+ this._counters.set(name, this._meter.createCounter(name));
111
+ }
112
+ return this._counters.get(name);
113
+ }
114
+
115
+ count(name, value = 1, attributes = {}, context) {
116
+ try {
117
+ this._getCounter(name).add(value, attributes);
118
+ } catch (_) {
119
+ // Telemetry must never break the wrapped path.
120
+ }
121
+ // Mirror onto the internal stream for the usage rollup + plugin taps; the
122
+ // bus context (incl. high-cardinality ids) rides the bus only, never the
123
+ // OTel metric attributes.
124
+ const merged = mergeTelemetryContext(context);
125
+ const payload = { name, value, attributes };
126
+ if (merged) payload.context = merged;
127
+ this._bus.emit('metric', payload);
128
+ }
129
+
130
+ event(name, attributes = {}, context) {
131
+ try {
132
+ const active = otelApi.trace.getActiveSpan();
133
+ if (active) active.addEvent(name, attributes);
134
+ } catch (_) {}
135
+ const merged = mergeTelemetryContext(context);
136
+ const payload = { name, attributes };
137
+ if (merged) payload.context = merged;
138
+ this._bus.emit('event', payload);
139
+ }
140
+
141
+ startSpan(name, options) {
142
+ return this._tracer.startSpan(name, options);
143
+ }
144
+
145
+ async span(name, fn, options = {}) {
146
+ return this._tracer.startActiveSpan(name, options, async (span) => {
147
+ try {
148
+ const result =
149
+ typeof fn === 'function' ? await fn(span) : undefined;
150
+ span.setStatus({ code: otelApi.SpanStatusCode.OK });
151
+ return result;
152
+ } catch (err) {
153
+ span.recordException(err);
154
+ span.setStatus({
155
+ code: otelApi.SpanStatusCode.ERROR,
156
+ message: err && err.message,
157
+ });
158
+ throw err;
159
+ } finally {
160
+ span.end();
161
+ }
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Run `fn` with the given identifiers on (a) the AsyncLocalStorage telemetry
167
+ * context — which the usage rollup reads to attribute emissions per-integration
168
+ * on ANY path — and (b) OTel baggage for trace propagation. High-cardinality
169
+ * ids ride here, never on metric labels.
170
+ */
171
+ async withContext(attributes = {}, fn) {
172
+ const entries = {};
173
+ for (const [key, value] of Object.entries(attributes)) {
174
+ if (value !== undefined && value !== null) {
175
+ entries[key] = { value: String(value) };
176
+ }
177
+ }
178
+ const baggage = otelApi.propagation.createBaggage(entries);
179
+ const ctx = otelApi.propagation.setBaggage(
180
+ otelApi.context.active(),
181
+ baggage
182
+ );
183
+ return runWithTelemetryContext(attributes, () =>
184
+ otelApi.context.with(ctx, () =>
185
+ typeof fn === 'function' ? fn() : undefined
186
+ )
187
+ );
188
+ }
189
+
190
+ on(eventType, callback) {
191
+ return this._bus.on(eventType, callback);
192
+ }
193
+
194
+ async forceFlush() {
195
+ await Promise.allSettled([
196
+ this._tracerProvider.forceFlush(),
197
+ this._meterProvider.forceFlush(),
198
+ ]);
199
+ }
200
+
201
+ async shutdown() {
202
+ await Promise.allSettled([
203
+ this._tracerProvider.shutdown(),
204
+ this._meterProvider.shutdown(),
205
+ ]);
206
+ }
207
+
208
+ isEnabled() {
209
+ return true;
210
+ }
211
+ }
212
+
213
+ module.exports = { OtelTelemetry };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Declarative plugin/extension telemetry taps.
3
+ *
4
+ * The built-in usage rollup and North Star derivation subscribe to the telemetry
5
+ * bus in framework code; this lets an ADOPTER subscribe declaratively from the
6
+ * app definition (`Definition.telemetry.subscribers: [...]`) — forward to a
7
+ * custom sink, compute aggregates, persist counters — without writing wiring code.
8
+ *
9
+ * A subscriber is one of:
10
+ * - a **factory** `fn(telemetry)` that registers itself (e.g. calls
11
+ * `telemetry.on('metric', ...)`) and MAY return an unsubscribe function; or
12
+ * - a **declarative object** `{ event?: 'metric'|'event', handler(payload, eventType) }`
13
+ * — when `event` is omitted the handler receives both metric and event payloads.
14
+ *
15
+ * Every attach and every handler invocation is guarded so a misbehaving
16
+ * subscriber can never break emission, its siblings, or a handler (mirrors the
17
+ * bus's own per-subscriber isolation).
18
+ *
19
+ * @param {object} params
20
+ * @param {object} params.telemetry Telemetry service exposing `on(eventType, cb)`.
21
+ * @param {Array<Function|{event?: string, handler: Function}>} [params.subscribers]
22
+ * @returns {Array<Function>} unsubscribe functions for the wired subscribers.
23
+ */
24
+ const EVENT_TYPES = ['metric', 'event'];
25
+
26
+ function wireTelemetrySubscribers({ telemetry, subscribers = [] } = {}) {
27
+ if (
28
+ !telemetry ||
29
+ typeof telemetry.on !== 'function' ||
30
+ !Array.isArray(subscribers)
31
+ ) {
32
+ return [];
33
+ }
34
+
35
+ const unsubscribes = [];
36
+
37
+ for (const subscriber of subscribers) {
38
+ try {
39
+ if (typeof subscriber === 'function') {
40
+ const off = subscriber(telemetry);
41
+ if (typeof off === 'function') unsubscribes.push(off);
42
+ continue;
43
+ }
44
+
45
+ if (subscriber && typeof subscriber.handler === 'function') {
46
+ const events = subscriber.event
47
+ ? [subscriber.event]
48
+ : EVENT_TYPES;
49
+ for (const eventType of events) {
50
+ const off = telemetry.on(eventType, (payload) => {
51
+ try {
52
+ subscriber.handler(payload, eventType);
53
+ } catch (error) {
54
+ console.warn(
55
+ `[Frigg][telemetry] subscriber handler for "${eventType}" threw: ${
56
+ error && error.message
57
+ }`
58
+ );
59
+ }
60
+ });
61
+ if (typeof off === 'function') unsubscribes.push(off);
62
+ }
63
+ }
64
+ } catch (error) {
65
+ // A single bad subscriber must never break wiring of the others.
66
+ console.warn(
67
+ `[Frigg][telemetry] failed to wire a telemetry subscriber: ${
68
+ error && error.message
69
+ }`
70
+ );
71
+ }
72
+ }
73
+
74
+ return unsubscribes;
75
+ }
76
+
77
+ module.exports = { wireTelemetrySubscribers };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Resolves and validates the telemetry section of an app definition
3
+ * into a normalized config the TelemetryService and
4
+ * usage rollup consume: `{ exporter, northStar, sampleRatio }`.
5
+ *
6
+ * Exporter default is keyed off STAGE: only a genuinely LOCAL run (STAGE=local)
7
+ * gets `console` (visible, free); every deployed stage — including `dev` —
8
+ * defaults to `none`. `dev` is a deployed AWS stage, and `console` there would
9
+ * write full spans to CloudWatch by default (cost + a data-exposure surface).
10
+ * Adopters opt into `console`/`otlp` explicitly for deployed stages.
11
+ */
12
+ const VALID_EXPORTER_TYPES = [
13
+ 'none',
14
+ 'noop',
15
+ 'console',
16
+ 'otlp',
17
+ 'honeycomb',
18
+ 'datadog',
19
+ ];
20
+
21
+ function normalizeExporterConfig(exporter, stage) {
22
+ if (!exporter) {
23
+ return stage === 'local' ? { type: 'console' } : { type: 'none' };
24
+ }
25
+
26
+ // Pre-built exporter instances (tests / advanced adopters) bypass type checks.
27
+ if (exporter.traceExporter || exporter.metricExporter) {
28
+ return exporter;
29
+ }
30
+
31
+ if (!VALID_EXPORTER_TYPES.includes(exporter.type)) {
32
+ throw new Error(
33
+ `[Frigg][telemetry] invalid exporter type "${exporter.type}". ` +
34
+ `Supported: ${VALID_EXPORTER_TYPES.join(', ')}`
35
+ );
36
+ }
37
+ return exporter;
38
+ }
39
+
40
+ // Fraction of TRACES exported (0..1, default 1) — a cost knob wired to the OTel
41
+ // sampler in otel-telemetry. Does NOT thin usage counters; those stay exact.
42
+ function resolveSampleRatio(sampleRatio) {
43
+ if (sampleRatio === undefined || sampleRatio === null) return 1;
44
+ if (
45
+ typeof sampleRatio !== 'number' ||
46
+ Number.isNaN(sampleRatio) ||
47
+ sampleRatio < 0 ||
48
+ sampleRatio > 1
49
+ ) {
50
+ throw new Error(
51
+ `[Frigg][telemetry] sampleRatio must be a number in [0,1], got ${sampleRatio}`
52
+ );
53
+ }
54
+ return sampleRatio;
55
+ }
56
+
57
+ function validateNorthStarEntry(entry, where) {
58
+ if (!entry || typeof entry.name !== 'string' || !entry.name) {
59
+ throw new Error(
60
+ `[Frigg][telemetry] northStar.${where} must reference a counter { name }`
61
+ );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Adopter-declared telemetry subscribers. Each is either a factory
67
+ * function `fn(telemetry)` or a declarative `{ event?, handler }` object. Kept as
68
+ * an array so `wireTelemetrySubscribers` can attach them to the bus at runtime.
69
+ */
70
+ function resolveSubscribers(subscribers) {
71
+ if (subscribers === undefined || subscribers === null) return [];
72
+ if (!Array.isArray(subscribers)) {
73
+ throw new Error(
74
+ '[Frigg][telemetry] subscribers must be an array of functions or { handler } objects'
75
+ );
76
+ }
77
+ for (const subscriber of subscribers) {
78
+ const ok =
79
+ typeof subscriber === 'function' ||
80
+ (subscriber && typeof subscriber.handler === 'function');
81
+ if (!ok) {
82
+ throw new Error(
83
+ '[Frigg][telemetry] each subscriber must be a function or an object with a handler function'
84
+ );
85
+ }
86
+ }
87
+ return subscribers;
88
+ }
89
+
90
+ function resolveNorthStar(northStar) {
91
+ if (!northStar) return null;
92
+ if (northStar.default) validateNorthStarEntry(northStar.default, 'default');
93
+ if (northStar.byType) {
94
+ for (const [type, entry] of Object.entries(northStar.byType)) {
95
+ validateNorthStarEntry(entry, `byType.${type}`);
96
+ }
97
+ }
98
+ return northStar;
99
+ }
100
+
101
+ /**
102
+ * @param {object} appDefinition The backend `Definition` export.
103
+ * @param {object} [ctx]
104
+ * @param {string} [ctx.stage] Deployment stage (defaults to STAGE/NODE_ENV).
105
+ * @returns {{ exporter: object, northStar: object|null, sampleRatio: number }}
106
+ */
107
+ function resolveTelemetryConfig(appDefinition = {}, ctx = {}) {
108
+ const stage =
109
+ ctx.stage || process.env.STAGE || process.env.NODE_ENV || 'production';
110
+ const telemetry = appDefinition.telemetry || {};
111
+
112
+ return {
113
+ exporter: normalizeExporterConfig(telemetry.exporter, stage),
114
+ northStar: resolveNorthStar(telemetry.northStar),
115
+ sampleRatio: resolveSampleRatio(telemetry.sampleRatio),
116
+ subscribers: resolveSubscribers(telemetry.subscribers),
117
+ };
118
+ }
119
+
120
+ module.exports = { resolveTelemetryConfig };
@@ -0,0 +1,40 @@
1
+ const { AsyncLocalStorage } = require('node:async_hooks');
2
+
3
+ /**
4
+ * Ambient telemetry context. A single process-wide AsyncLocalStorage
5
+ * holds the standard identifiers ({integrationId, integrationType, userId,
6
+ * version, ...}) for the duration of a handler, set once at the handler seam via
7
+ * `telemetry.withContext(...)`. Any emission during that async scope — including
8
+ * deep calls like an API-module request — reads the ambient context, so the
9
+ * usage rollup can attribute counters per-integration WITHOUT threading context
10
+ * through every call and WITHOUT relying on OTel baggage (which does not exist
11
+ * on the no-op path). This is the propagation mechanism the plan intended.
12
+ *
13
+ * Only bus-payload `context` is populated from here; high-cardinality ids never
14
+ * touch OTel metric labels.
15
+ */
16
+ const store = new AsyncLocalStorage();
17
+
18
+ function runWithTelemetryContext(context, fn) {
19
+ return store.run(context || {}, fn);
20
+ }
21
+
22
+ function getTelemetryContextStore() {
23
+ return store.getStore() || null;
24
+ }
25
+
26
+ /**
27
+ * Merge the ambient context with an explicit per-call context (explicit keys
28
+ * win, e.g. a request `url`). Returns undefined when there is nothing to attach.
29
+ */
30
+ function mergeTelemetryContext(explicit) {
31
+ const ambient = store.getStore();
32
+ if (!ambient && !explicit) return undefined;
33
+ return { ...(ambient || {}), ...(explicit || {}) };
34
+ }
35
+
36
+ module.exports = {
37
+ runWithTelemetryContext,
38
+ getTelemetryContextStore,
39
+ mergeTelemetryContext,
40
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * TelemetryEventBus — the internal event stream telemetry flows onto.
3
+ * Plugins/extensions and the built-in usage rollup
4
+ * subscribe here; it is deliberately independent of OTel export (Decision 7),
5
+ * so usage counters persist even when the OTel exporter is a no-op.
6
+ *
7
+ * This is intentionally a **synchronous** in-process fan-out (Frigg runs one
8
+ * invocation per Lambda container): a plain subscriber list with per-subscriber
9
+ * `try/catch` so a misbehaving subscriber can never break emission or its
10
+ * siblings. No queue/backpressure machinery — emission volume is bounded by a
11
+ * single invocation's work.
12
+ *
13
+ * ## Public event contract (semver-stable)
14
+ * Event types and their payload shapes:
15
+ * - `'metric'` → `{ name: string, value: number, attributes: object, context?: object }`
16
+ * - `'event'` → `{ name: string, attributes: object, context?: object }`
17
+ * `attributes` are the bounded OTel metric labels. `context` (present when an
18
+ * ambient telemetry context is active) carries the high-cardinality identifiers
19
+ * — {integrationId, integrationType, userId, version, ...} plus per-call extras
20
+ * like a request `url` — which the usage rollup reads for attribution. Those ids
21
+ * NEVER appear in `attributes` (Cardinality note).
22
+ */
23
+ function createTelemetryEventBus() {
24
+ /** @type {Map<string, Set<Function>>} */
25
+ const subscribers = new Map();
26
+
27
+ function on(eventType, callback) {
28
+ if (!subscribers.has(eventType)) {
29
+ subscribers.set(eventType, new Set());
30
+ }
31
+ const set = subscribers.get(eventType);
32
+ set.add(callback);
33
+ return function off() {
34
+ set.delete(callback);
35
+ };
36
+ }
37
+
38
+ function emit(eventType, payload) {
39
+ const set = subscribers.get(eventType);
40
+ if (!set || set.size === 0) return;
41
+ for (const callback of set) {
42
+ try {
43
+ callback(payload);
44
+ } catch (error) {
45
+ // A subscriber must never break emission or its siblings.
46
+ console.warn(
47
+ `[Frigg][telemetry] subscriber for "${eventType}" threw: ${
48
+ error && error.message
49
+ }`
50
+ );
51
+ }
52
+ }
53
+ }
54
+
55
+ return { on, emit };
56
+ }
57
+
58
+ module.exports = { createTelemetryEventBus };
@@ -0,0 +1,147 @@
1
+ // The process composition root for telemetry: one shared state, per-part lazy
2
+ // getters, one atomic reset — replacing three separate singleton module caches
3
+ // (telemetry / usage-rollup / plugin-subscribers) that previously had to be
4
+ // reset together in the right order. Bus identity across the parts is guaranteed
5
+ // by construction: the rollup and plugin subscribers attach to the same
6
+ // getTelemetry() instance reached through this module's own closure.
7
+
8
+ const { createTelemetry } = require('./telemetry-service');
9
+ const { NoOpTelemetry } = require('./no-op-telemetry');
10
+
11
+ const UNSET = Symbol('unset');
12
+
13
+ function newState() {
14
+ return { telemetry: null, usageRollup: UNSET, pluginSubscribers: UNSET };
15
+ }
16
+ let state = newState();
17
+
18
+ function loadTelemetryConfig() {
19
+ // Lazy-required to avoid a load-time cycle with the handlers layer.
20
+ const { loadAppDefinition } = require('../handlers/app-definition-loader');
21
+ return loadAppDefinition();
22
+ }
23
+
24
+ /**
25
+ * The process telemetry service. Created lazily on first use and reused for the
26
+ * life of the Lambda container, so the OTel SDK initialises at most once per cold
27
+ * start. Never throws — any failure falls back to the no-op.
28
+ */
29
+ function getTelemetry() {
30
+ if (state.telemetry) return state.telemetry;
31
+ try {
32
+ const { telemetry = {} } = loadTelemetryConfig();
33
+ const stage = process.env.STAGE || process.env.NODE_ENV || 'production';
34
+ state.telemetry = createTelemetry({
35
+ exporter: telemetry.exporter,
36
+ sampleRatio: telemetry.sampleRatio,
37
+ resource: { service: process.env.FRIGG_STACK || 'frigg', stage },
38
+ });
39
+ } catch (_) {
40
+ state.telemetry = new NoOpTelemetry();
41
+ }
42
+ return state.telemetry;
43
+ }
44
+
45
+ /**
46
+ * The process usage-rollup subscriber, wired to getTelemetry()'s bus. Returns
47
+ * null when usage is disabled (no Definition.usage opt-in and no North Star) or
48
+ * the app definition can't load. Never throws — the warn lets operators tell
49
+ * "usage off" from "usage broken" (e.g. unresolved DB_TYPE).
50
+ */
51
+ function getUsageRollupSubscriber() {
52
+ if (state.usageRollup !== UNSET) return state.usageRollup;
53
+ state.usageRollup = null;
54
+ try {
55
+ const { computeTrackedMetrics } = require('../usage/tracked-metrics');
56
+ const {
57
+ northStarKeys,
58
+ createNorthStarDerivationSubscriber,
59
+ } = require('./north-star');
60
+ const { integrations = [], telemetry = {} } = loadTelemetryConfig();
61
+
62
+ // Track both Definition.usage opt-ins and any North Star counter keys,
63
+ // so a derived North Star persists even without an explicit usage decl.
64
+ const trackedMetrics = computeTrackedMetrics(integrations);
65
+ for (const key of northStarKeys(telemetry.northStar)) {
66
+ trackedMetrics.add(key);
67
+ }
68
+ if (trackedMetrics.size === 0) return state.usageRollup;
69
+
70
+ const {
71
+ createUsageRepository,
72
+ } = require('../usage/repositories/usage-repository-factory');
73
+ const {
74
+ createUsageRollupSubscriber,
75
+ } = require('./usage-rollup-subscriber');
76
+ const telemetryService = getTelemetry(); // same memo → shared bus
77
+
78
+ state.usageRollup = createUsageRollupSubscriber({
79
+ telemetry: telemetryService,
80
+ usageRepository: createUsageRepository(),
81
+ trackedMetrics,
82
+ });
83
+
84
+ // North Star derived-from-trace: emits the North Star counter when a
85
+ // configured signal matches; the emission flows to the rollup above.
86
+ if (telemetry.northStar) {
87
+ createNorthStarDerivationSubscriber({
88
+ telemetry: telemetryService,
89
+ northStar: telemetry.northStar,
90
+ });
91
+ }
92
+ } catch (error) {
93
+ console.warn(
94
+ `[Frigg][usage] rollup disabled: ${error && error.message}`
95
+ );
96
+ state.usageRollup = null;
97
+ }
98
+ return state.usageRollup;
99
+ }
100
+
101
+ /**
102
+ * Adopter-declared telemetry subscribers, wired to getTelemetry()'s bus once per
103
+ * cold start. Independent of the usage rollup (adopter taps wire even when no
104
+ * integration declares Definition.usage). Never throws.
105
+ */
106
+ function getPluginTelemetrySubscribers() {
107
+ if (state.pluginSubscribers !== UNSET) return state.pluginSubscribers;
108
+ state.pluginSubscribers = [];
109
+ try {
110
+ const { wireTelemetrySubscribers } = require('./plugin-subscribers');
111
+ const { telemetry = {} } = loadTelemetryConfig();
112
+ const subscribers = telemetry.subscribers || [];
113
+ if (subscribers.length === 0) return state.pluginSubscribers;
114
+
115
+ state.pluginSubscribers = wireTelemetrySubscribers({
116
+ telemetry: getTelemetry(),
117
+ subscribers,
118
+ });
119
+ } catch (error) {
120
+ console.warn(
121
+ `[Frigg][telemetry] plugin subscribers disabled: ${
122
+ error && error.message
123
+ }`
124
+ );
125
+ state.pluginSubscribers = [];
126
+ }
127
+ return state.pluginSubscribers;
128
+ }
129
+
130
+ /** Test-only: reset the whole runtime — no stale cross-memo states possible. */
131
+ function resetTelemetryRuntimeForTests() {
132
+ state = newState();
133
+ }
134
+
135
+ /** Test-only: install a telemetry service; dependent memos clear atomically. */
136
+ function setTelemetryForTests(telemetry) {
137
+ state = newState();
138
+ state.telemetry = telemetry;
139
+ }
140
+
141
+ module.exports = {
142
+ getTelemetry,
143
+ getUsageRollupSubscriber,
144
+ getPluginTelemetrySubscribers,
145
+ resetTelemetryRuntimeForTests,
146
+ setTelemetryForTests,
147
+ };