@friggframework/core 2.0.0--canary.703.12b980b.0 → 2.0.0--canary.707.df80bfd.0

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 (308) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +28 -0
  3. package/README.md +105 -0
  4. package/application/commands/integration-commands.js +20 -10
  5. package/application/commands/scheduler-commands.js +11 -7
  6. package/application/commands/user-commands.js +17 -5
  7. package/application/index.js +6 -2
  8. package/core/Worker.js +31 -7
  9. package/core/create-handler.js +9 -0
  10. package/credential/repositories/credential-repository-documentdb.js +34 -0
  11. package/credential/repositories/credential-repository-interface.js +15 -0
  12. package/credential/repositories/credential-repository-mongo.js +23 -0
  13. package/credential/repositories/credential-repository-postgres.js +28 -0
  14. package/credential/use-cases/credential-operations-for-user.js +202 -0
  15. package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
  16. package/database/config.js +48 -24
  17. package/database/documentdb-encryption-service.js +9 -22
  18. package/database/documentdb-utils.js +2 -2
  19. package/database/encryption/README.md +78 -14
  20. package/database/encryption/documentdb-encryption-service.md +78 -1
  21. package/database/encryption/encryption-config.js +219 -0
  22. package/database/encryption/encryption-schema-registry.js +121 -24
  23. package/database/encryption/field-encryption-service.js +69 -5
  24. package/database/encryption/integration-mapping-encryption.js +1 -1
  25. package/database/encryption/test-support/fake-ciphertext.js +15 -0
  26. package/database/organization-where.js +18 -0
  27. package/database/prisma.js +15 -38
  28. package/database/unique-violation.js +25 -0
  29. package/database/use-cases/check-encryption-health-use-case.js +43 -52
  30. package/errors/index.js +0 -4
  31. package/extensions/bootstrap-runner.js +43 -0
  32. package/extensions/extension-loader.js +230 -0
  33. package/extensions/index.js +44 -0
  34. package/extensions/initialize-app.js +123 -0
  35. package/extensions/prisma-composer.js +270 -0
  36. package/extensions/route-mounter.js +103 -0
  37. package/extensions/schema-composer.js +188 -0
  38. package/generated/prisma-mongodb/edge.js +33 -42
  39. package/generated/prisma-mongodb/index-browser.js +30 -39
  40. package/generated/prisma-mongodb/index.d.ts +3187 -2005
  41. package/generated/prisma-mongodb/index.js +33 -42
  42. package/generated/prisma-mongodb/package.json +1 -1
  43. package/generated/prisma-mongodb/schema.prisma +86 -59
  44. package/generated/prisma-mongodb/wasm.js +33 -42
  45. package/generated/prisma-postgresql/edge.js +33 -42
  46. package/generated/prisma-postgresql/index-browser.js +30 -39
  47. package/generated/prisma-postgresql/index.d.ts +3380 -2177
  48. package/generated/prisma-postgresql/index.js +33 -42
  49. package/generated/prisma-postgresql/package.json +1 -1
  50. package/generated/prisma-postgresql/schema.prisma +81 -57
  51. package/generated/prisma-postgresql/wasm.js +33 -42
  52. package/handlers/WEBHOOKS.md +0 -29
  53. package/handlers/app-definition-loader.js +128 -19
  54. package/handlers/app-handler-helpers.js +27 -8
  55. package/handlers/backend-utils.js +31 -25
  56. package/handlers/integration-event-dispatcher.js +3 -7
  57. package/handlers/middleware/admin-auth.js +14 -1
  58. package/handlers/routers/auth.js +46 -10
  59. package/handlers/routers/health.js +13 -4
  60. package/handlers/routers/integration-defined-routers.js +144 -92
  61. package/handlers/routers/integration-webhook-routers.js +100 -50
  62. package/handlers/workers/integration-defined-workers.js +5 -2
  63. package/index.js +66 -16
  64. package/infrastructure/scheduler/index.js +19 -6
  65. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  66. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  67. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  68. package/integrations/index.js +1 -14
  69. package/integrations/integration-base.js +2 -163
  70. package/integrations/integration-router.js +132 -102
  71. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  72. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  73. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  74. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  75. package/integrations/repositories/integration-mapping-repository.js +0 -29
  76. package/integrations/repositories/integration-not-found.js +14 -0
  77. package/integrations/repositories/integration-repository-documentdb.js +37 -9
  78. package/integrations/repositories/integration-repository-interface.js +17 -4
  79. package/integrations/repositories/integration-repository-mongo.js +64 -96
  80. package/integrations/repositories/integration-repository-postgres.js +63 -81
  81. package/integrations/repositories/process-repository-mongo.js +1 -1
  82. package/integrations/schedule-config.js +80 -0
  83. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  84. package/integrations/use-cases/create-integration.js +56 -31
  85. package/integrations/use-cases/delete-integration-for-user.js +4 -4
  86. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  87. package/integrations/use-cases/get-integration-for-user.js +2 -4
  88. package/integrations/use-cases/get-integration-instance.js +4 -4
  89. package/integrations/use-cases/get-integrations-for-user.js +79 -39
  90. package/integrations/use-cases/get-possible-integrations.js +2 -7
  91. package/integrations/use-cases/index.js +7 -1
  92. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  93. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  94. package/integrations/use-cases/update-integration.js +4 -4
  95. package/integrations/utils/map-integration-dto.js +43 -14
  96. package/management-api/create-management-api-router.js +49 -0
  97. package/management-api/error-response.js +116 -0
  98. package/management-api/management-api-config.js +151 -0
  99. package/management-api/meta-router.js +76 -0
  100. package/management-api/openapi/build-openapi.js +144 -0
  101. package/management-api/openapi/components.js +415 -0
  102. package/management-api/openapi/v1-operations.js +83 -0
  103. package/management-api/openapi/v2-operations.js +264 -0
  104. package/management-api/route-registry.js +411 -0
  105. package/management-api/test-support/http-client.js +39 -0
  106. package/management-api/use-cases/get-management-api-meta.js +57 -0
  107. package/management-api/v1-deprecation.js +120 -0
  108. package/management-api/v2/create-v2-router.js +85 -0
  109. package/management-api/v2/dependencies.js +281 -0
  110. package/management-api/v2/dto.js +143 -0
  111. package/management-api/v2/handlers/authorization.js +94 -0
  112. package/management-api/v2/handlers/credentials.js +33 -0
  113. package/management-api/v2/handlers/entities.js +77 -0
  114. package/management-api/v2/handlers/integrations.js +128 -0
  115. package/management-api/v2/handlers/proxy.js +34 -0
  116. package/management-api/v2/request.js +85 -0
  117. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  118. package/modules/domain/authorization-session.js +144 -0
  119. package/modules/entity-scope.js +15 -0
  120. package/modules/entity-type-registry.js +311 -0
  121. package/modules/index.js +14 -0
  122. package/modules/module-factory.js +12 -0
  123. package/modules/module.js +53 -2
  124. package/modules/proxy/proxy-bases.js +169 -0
  125. package/modules/proxy/proxy-errors.js +31 -0
  126. package/modules/proxy/proxy-path.js +183 -0
  127. package/modules/proxy/proxy-policy.js +277 -0
  128. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  129. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  130. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  131. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  132. package/modules/repositories/entity-not-found.js +18 -0
  133. package/modules/repositories/module-repository-documentdb.js +51 -6
  134. package/modules/repositories/module-repository-interface.js +19 -2
  135. package/modules/repositories/module-repository-mongo.js +67 -74
  136. package/modules/repositories/module-repository-postgres.js +66 -74
  137. package/modules/repositories/module-repository.js +2 -1
  138. package/modules/requester/proxy-request.js +245 -0
  139. package/modules/requester/requester.js +35 -5
  140. package/modules/scope-config.js +192 -0
  141. package/modules/scope-policy.js +112 -0
  142. package/modules/scope-resolution.js +288 -0
  143. package/modules/scope-visibility.js +247 -0
  144. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  145. package/modules/tests/doubles/ownership-matching.js +18 -0
  146. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  147. package/modules/use-cases/create-entity-from-credential.js +113 -0
  148. package/modules/use-cases/entity-operations-for-user.js +162 -0
  149. package/modules/use-cases/entity-types.js +139 -0
  150. package/modules/use-cases/execute-entity-proxy-request.js +331 -0
  151. package/modules/use-cases/get-authorization-requirements.js +100 -0
  152. package/modules/use-cases/get-entities-for-user.js +26 -13
  153. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  154. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  155. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  156. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  157. package/modules/use-cases/get-module.js +10 -4
  158. package/modules/use-cases/manage-access.js +21 -0
  159. package/modules/use-cases/multi-step-authorization.js +329 -0
  160. package/modules/use-cases/process-authorization-callback.js +53 -19
  161. package/modules/use-cases/refresh-entity-options.js +10 -4
  162. package/modules/use-cases/test-module-auth.js +10 -4
  163. package/package.json +6 -7
  164. package/prisma-mongodb/schema.prisma +86 -59
  165. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  166. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  167. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  168. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  169. package/prisma-postgresql/schema.prisma +81 -57
  170. package/providers/index.js +23 -0
  171. package/providers/resolve-provider.js +93 -0
  172. package/providers/runtime-detection.js +124 -0
  173. package/queues/index.js +25 -0
  174. package/queues/providers/index.js +11 -0
  175. package/queues/providers/netlify-background-provider.js +150 -0
  176. package/queues/providers/qstash-queue-provider.js +219 -0
  177. package/queues/providers/sqs-queue-provider.js +70 -0
  178. package/queues/queue-client-interface.js +55 -0
  179. package/queues/queue-message-auth.js +193 -0
  180. package/queues/queue-provider-factory.js +101 -0
  181. package/queues/queue-provider.js +65 -0
  182. package/queues/queuer-util.js +2 -9
  183. package/queues/sqs-queue-client.js +81 -0
  184. package/types/index.d.ts +2 -0
  185. package/types/modules/index.d.ts +214 -0
  186. package/types/user/index.d.ts +134 -0
  187. package/user/adopter-jwt-config.js +177 -0
  188. package/user/adopter-jwt-errors.js +59 -0
  189. package/user/adopter-token-verifier.js +122 -0
  190. package/user/app-user-helpers.js +97 -0
  191. package/user/create-user-authentication.js +64 -0
  192. package/user/membership.js +51 -0
  193. package/user/repositories/organization-membership-record.js +19 -0
  194. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  195. package/user/repositories/organization-membership-repository-factory.js +42 -0
  196. package/user/repositories/organization-membership-repository-interface.js +57 -0
  197. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  198. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  199. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  200. package/user/repositories/user-repository-documentdb.js +27 -2
  201. package/user/repositories/user-repository-interface.js +18 -0
  202. package/user/repositories/user-repository-mongo.js +24 -0
  203. package/user/repositories/user-repository-postgres.js +18 -0
  204. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  205. package/user/use-cases/authenticate-user.js +7 -1
  206. package/user/use-cases/ensure-organization-membership.js +92 -0
  207. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  208. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  209. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  210. package/user/user.js +64 -1
  211. package/errors/mapping-already-exists-error.js +0 -14
  212. package/handlers/workers/integration-triggers.js +0 -25
  213. package/infrastructure/scheduler/recurrence.js +0 -204
  214. package/integrations/define/DESIGN.md +0 -122
  215. package/integrations/define/config.js +0 -98
  216. package/integrations/define/define-app.js +0 -66
  217. package/integrations/define/define-extension.js +0 -154
  218. package/integrations/define/define-integration.js +0 -335
  219. package/integrations/define/examples/deal-alerts.js +0 -140
  220. package/integrations/define/extension-binding.js +0 -174
  221. package/integrations/define/index.js +0 -11
  222. package/integrations/define/json-schema-lite.js +0 -68
  223. package/integrations/define/subscriptions.js +0 -186
  224. package/integrations/define/tests/fixtures/hubspot.js +0 -109
  225. package/integrations/define/tests/fixtures/slack.js +0 -26
  226. package/integrations/define/use.js +0 -39
  227. package/integrations/definitions/README.md +0 -278
  228. package/integrations/definitions/canonical.js +0 -11
  229. package/integrations/definitions/capabilities/AUDIT.md +0 -480
  230. package/integrations/definitions/capabilities/acmeerp.capabilities.json +0 -633
  231. package/integrations/definitions/capabilities/asana.capabilities.json +0 -279
  232. package/integrations/definitions/capabilities/calendly.capabilities.json +0 -385
  233. package/integrations/definitions/capabilities/docusign.capabilities.json +0 -314
  234. package/integrations/definitions/capabilities/fathom.capabilities.json +0 -238
  235. package/integrations/definitions/capabilities/github.capabilities.json +0 -585
  236. package/integrations/definitions/capabilities/hubspot.capabilities.json +0 -822
  237. package/integrations/definitions/capabilities/jira.capabilities.json +0 -391
  238. package/integrations/definitions/capabilities/klaviyo.capabilities.json +0 -503
  239. package/integrations/definitions/capabilities/linear.capabilities.json +0 -378
  240. package/integrations/definitions/capabilities/netsuite.capabilities.json +0 -535
  241. package/integrations/definitions/capabilities/notion.capabilities.json +0 -208
  242. package/integrations/definitions/capabilities/phone.capabilities.json +0 -511
  243. package/integrations/definitions/capabilities/pipedrive.capabilities.json +0 -430
  244. package/integrations/definitions/capabilities/quickbooks.capabilities.json +0 -635
  245. package/integrations/definitions/capabilities/salesforce.capabilities.json +0 -568
  246. package/integrations/definitions/capabilities/shopify.capabilities.json +0 -1128
  247. package/integrations/definitions/capabilities/slack.capabilities.json +0 -373
  248. package/integrations/definitions/capabilities/stripe.capabilities.json +0 -630
  249. package/integrations/definitions/capabilities/webflow.capabilities.json +0 -196
  250. package/integrations/definitions/capabilities/zendesk.capabilities.json +0 -422
  251. package/integrations/definitions/compile.js +0 -80
  252. package/integrations/definitions/config-form.js +0 -137
  253. package/integrations/definitions/examples/acme-erp-quickbooks.definition.json +0 -467
  254. package/integrations/definitions/examples/asana-slack.definition.json +0 -219
  255. package/integrations/definitions/examples/calendly-salesforce.definition.json +0 -459
  256. package/integrations/definitions/examples/docusign-salesforce.definition.json +0 -305
  257. package/integrations/definitions/examples/fathom-hubspot-notion.definition.json +0 -575
  258. package/integrations/definitions/examples/github-linear.definition.json +0 -322
  259. package/integrations/definitions/examples/hubspot-slack.definition.json +0 -427
  260. package/integrations/definitions/examples/klaviyo-hubspot.definition.json +0 -336
  261. package/integrations/definitions/examples/phone-hubspot.definition.json +0 -633
  262. package/integrations/definitions/examples/pipedrive-hubspot.definition.json +0 -136
  263. package/integrations/definitions/examples/pipedrive-phone.definition.json +0 -340
  264. package/integrations/definitions/examples/records/phone-hubspot.definition-version.json +0 -652
  265. package/integrations/definitions/examples/records/phone-hubspot.integration.json +0 -86
  266. package/integrations/definitions/examples/records/phone-hubspot.mappings.json +0 -39
  267. package/integrations/definitions/examples/records/phone-hubspot.workflow-run.json +0 -62
  268. package/integrations/definitions/examples/shopify-klaviyo.definition.json +0 -488
  269. package/integrations/definitions/examples/shopify-netsuite.definition.json +0 -622
  270. package/integrations/definitions/examples/stripe-quickbooks.definition.json +0 -476
  271. package/integrations/definitions/examples/webflow-hubspot-slack.definition.json +0 -262
  272. package/integrations/definitions/examples/zendesk-jira.definition.json +0 -604
  273. package/integrations/definitions/expressions.js +0 -229
  274. package/integrations/definitions/index.js +0 -48
  275. package/integrations/definitions/integration-runtime.js +0 -779
  276. package/integrations/definitions/interpreter.js +0 -104
  277. package/integrations/definitions/manifests.js +0 -177
  278. package/integrations/definitions/renderers/common.js +0 -67
  279. package/integrations/definitions/renderers/html.js +0 -129
  280. package/integrations/definitions/renderers/hubspot.js +0 -59
  281. package/integrations/definitions/renderers/preview.js +0 -154
  282. package/integrations/definitions/renderers/slack.js +0 -146
  283. package/integrations/definitions/repositories/definition-version-repository.js +0 -67
  284. package/integrations/definitions/repositories/workflow-run-repository.js +0 -80
  285. package/integrations/definitions/router.js +0 -170
  286. package/integrations/definitions/runtime.js +0 -268
  287. package/integrations/definitions/schema/capability-manifest.v0.1.schema.json +0 -549
  288. package/integrations/definitions/schema/installation.v0.1.schema.json +0 -130
  289. package/integrations/definitions/schema/integration-definition.v0.1.schema.json +0 -909
  290. package/integrations/definitions/schema/records.md +0 -105
  291. package/integrations/definitions/schema-check.js +0 -58
  292. package/integrations/definitions/scripts/check-manifests.js +0 -220
  293. package/integrations/definitions/scripts/validate.js +0 -66
  294. package/integrations/definitions/steps.js +0 -123
  295. package/integrations/definitions/sync-engine.js +0 -297
  296. package/integrations/definitions/tests/helpers-fake-crm.js +0 -49
  297. package/integrations/definitions/transforms.js +0 -91
  298. package/integrations/definitions/use-cases/definition-use-cases.js +0 -245
  299. package/integrations/definitions/validate.js +0 -436
  300. package/integrations/definitions/versioning.js +0 -176
  301. package/integrations/definitions/webhooks.js +0 -119
  302. package/integrations/integration-types.js +0 -73
  303. package/integrations/triggers.js +0 -244
  304. package/integrations/use-cases/run-trigger-tick.js +0 -155
  305. package/integrations/use-cases/run-trigger.js +0 -77
  306. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +0 -66
  307. package/prisma-postgresql/migrations/20261019000000_workflow_run_waits/migration.sql +0 -7
  308. package/prisma-postgresql/migrations/20261019120000_definition_workspace/migration.sql +0 -5
@@ -2,21 +2,27 @@ const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
2
2
  const path = require('node:path');
3
3
  const fs = require('fs-extra');
4
4
  const { resolveTelemetryConfig } = require('../telemetry/telemetry-config');
5
+ const { assertAdopterJwtConfig } = require('../user/adopter-jwt-config');
5
6
  const { registerDeniedKeys } = require('../logs/denied-keys');
6
7
  const {
7
8
  extractCredentialFieldsFromModules,
8
9
  } = require('../database/encryption/encryption-schema-registry');
9
10
  const {
10
- getModulesDefinitionFromIntegrationClasses,
11
- } = require('../integrations/utils/map-integration-dto');
11
+ getEntityTypeRegistry,
12
+ getEntityTypeDefinitions,
13
+ } = require('../modules/entity-type-registry');
14
+ const { resolveOwnershipConfig } = require('../modules/entity-scope');
15
+ const { assertProxyConfig } = require('../modules/proxy/proxy-bases');
16
+ const {
17
+ resolveManagementApiConfig,
18
+ } = require('../management-api/management-api-config');
19
+ const { resolveScheduleConfig } = require('../integrations/schedule-config');
12
20
 
13
21
  // Handlers without a database never load the encryption registry, so the
14
22
  // credential leaf keys are registered for redaction here too.
15
- function registerCredentialLogKeys(appDefinition, integrations) {
23
+ function registerCredentialLogKeys(appDefinition, moduleDefinitions) {
16
24
  try {
17
- extractCredentialFieldsFromModules(
18
- getModulesDefinitionFromIntegrationClasses(integrations)
19
- );
25
+ extractCredentialFieldsFromModules(moduleDefinitions);
20
26
  } catch {
21
27
  // An odd integration shape must not stop the app from loading.
22
28
  }
@@ -29,18 +35,53 @@ function registerCredentialLogKeys(appDefinition, integrations) {
29
35
  }
30
36
 
31
37
  /**
32
- * Loads the App definition from the nearest backend package
33
- * @function loadAppDefinition
34
- * @description Searches for the nearest backend package.json, loads the corresponding index.js file,
35
- * and extracts the application definition containing integrations and user configuration.
36
- * @returns {{integrations: Array<object>, userConfig: object | null, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null}} An object containing the application definition.
37
- * @throws {Error} Throws error if backend package.json cannot be found.
38
- * @throws {Error} Throws error if index.js file cannot be found in the backend directory.
39
- * @example
40
- * const { integrations, userConfig, telemetry } = loadAppDefinition();
41
- * console.log(`Found ${integrations.length} integrations`);
38
+ * The definition handed to setAppDefinition(), or null when the backend is
39
+ * discovered from process.cwd().
42
40
  */
43
- function loadAppDefinition() {
41
+ let presetDefinition = null;
42
+
43
+ /**
44
+ * Pre-set the app definition so loadAppDefinition() does not discover the
45
+ * backend through process.cwd().
46
+ *
47
+ * Platforms such as Netlify need this: the function bundler (nft/esbuild)
48
+ * only traces static require() calls, and at runtime process.cwd() is the
49
+ * function root, not the backend. A generated function entry point therefore
50
+ * requires the backend's index.js statically and passes its Definition here.
51
+ *
52
+ * The preset definition is validated and derived exactly like a discovered
53
+ * one: adopter-JWT config, the entity registry, ownership and the Management
54
+ * API config all apply, so a Netlify function and an AWS Lambda load the
55
+ * same app the same way. Pass null to clear it.
56
+ *
57
+ * @param {Object|null} definition - the backend's `Definition` export
58
+ */
59
+ function setAppDefinition(definition) {
60
+ if (
61
+ definition !== null &&
62
+ (typeof definition !== 'object' || Array.isArray(definition))
63
+ ) {
64
+ throw new TypeError(
65
+ 'setAppDefinition expects the app Definition object (or null)'
66
+ );
67
+ }
68
+ presetDefinition = definition;
69
+ }
70
+
71
+ /**
72
+ * The definition preset with setAppDefinition(), unvalidated, or null.
73
+ *
74
+ * For low-level readers that only need one raw field before the app is
75
+ * fully loaded (the database type, the custom encryption schema) and keep
76
+ * their own process.cwd() discovery as the fallback.
77
+ *
78
+ * @returns {Object|null}
79
+ */
80
+ function getPresetAppDefinition() {
81
+ return presetDefinition;
82
+ }
83
+
84
+ function discoverAppDefinition() {
44
85
  const backendPath = findNearestBackendPackageJson();
45
86
  if (!backendPath) {
46
87
  throw new Error('Could not find backend package.json');
@@ -53,18 +94,78 @@ function loadAppDefinition() {
53
94
  }
54
95
 
55
96
  const backendJsFile = require(backendFilePath);
56
- const appDefinition = backendJsFile.Definition;
97
+ return backendJsFile.Definition;
98
+ }
57
99
 
100
+ /**
101
+ * Loads the App definition: the one preset with setAppDefinition(), or the
102
+ * nearest backend package's index.js.
103
+ * @function loadAppDefinition
104
+ * @description Uses the definition preset by setAppDefinition() when there is one; otherwise searches for the nearest backend package.json, loads the corresponding index.js file,
105
+ * and extracts the application definition containing integrations and user configuration.
106
+ * @returns {{appDefinition: object, integrations: Array<object>, entities: object | null, entityTypeRegistry: Map<string, object>, moduleDefinitions: Array<object>, ownership: {scope: ({default: string, allow: string[]}|null), individualAcross: string}, userConfig: object | null, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null, managementApi: object}} An object containing the application definition. `appDefinition` is the raw Definition object.
107
+ * @throws {Error} Throws error if backend package.json cannot be found.
108
+ * @throws {Error} Throws error if index.js file cannot be found in the backend directory.
109
+ * @throws {Error} Throws error if user.authModes.adopterJwt is enabled without a usable user.jwtConfig.
110
+ * @throws {OwnershipConfigError} Throws if an ownership or scope setting is invalid (ADR-056 §3.1).
111
+ * @throws {ManagementApiConfigError} Throws if `managementApi.proxy.scopes` is invalid (ADR-056 §3.5).
112
+ * @throws {ProxyConfigError} Throws if a module's `proxy.baseUrls` or an allow rule's `base` is invalid (ADR-052).
113
+ * @throws {ScheduleConfigError} Throws if an integration's `Definition.schedule` is invalid.
114
+ * @example
115
+ * const { integrations, userConfig, telemetry } = loadAppDefinition();
116
+ * console.log(`Found ${integrations.length} integrations`);
117
+ */
118
+ function loadAppDefinition() {
119
+ const appDefinition = presetDefinition || discoverAppDefinition();
120
+ return resolveAppDefinition(appDefinition);
121
+ }
122
+
123
+ function resolveAppDefinition(appDefinition) {
58
124
  const {
59
125
  integrations = [],
126
+ entities = null,
60
127
  user: userConfig = null,
61
128
  adminScripts = [],
62
129
  reports = [],
63
130
  admin = {},
64
131
  logging = null,
132
+ managementApi = {},
65
133
  } = appDefinition;
66
134
 
67
- registerCredentialLogKeys(appDefinition, integrations);
135
+ // Fail at load, not on the first request: an adopter-token mode with no
136
+ // key source would otherwise start cleanly and answer every login with 500.
137
+ assertAdopterJwtConfig(userConfig);
138
+
139
+ // Fail fast: a bad `entities` registry (key/name mismatch, conflicting
140
+ // definitions, `$infer: false` with an unregistered module) must stop the
141
+ // app at load, not on the first request that needs a module.
142
+ const entityTypeRegistry = getEntityTypeRegistry(appDefinition);
143
+ const moduleDefinitions = getEntityTypeDefinitions(entityTypeRegistry);
144
+
145
+ // Same reason for entity scopes (ADR-056 §3.1): a mistyped scope, an unknown
146
+ // `individualAcross`, or an organization-owned integration that binds an
147
+ // individual entity stops the app at load.
148
+ const ownership = resolveOwnershipConfig(appDefinition, entityTypeRegistry);
149
+
150
+ // And for the Management API: `managementApi.proxy.scopes` decides whose
151
+ // credentials the entity proxy may act through (ADR-056 §3.5), so an
152
+ // unknown scope stops the app here rather than being read as a default.
153
+ const managementApiConfig = resolveManagementApiConfig(managementApi);
154
+
155
+ // Named proxy bases (ADR-052 amendment): a module that declares both
156
+ // `baseUrl` and `baseUrls`, a bad base URL or name, or an allow rule for a
157
+ // base the module lacks, stops the app here.
158
+ assertProxyConfig(moduleDefinitions, managementApiConfig.proxy);
159
+
160
+ // And for each integration's `Definition.schedule`: a mistyped scope would
161
+ // otherwise tick ONGOING_SYNC per class when per installation was meant.
162
+ for (const IntegrationClass of integrations) {
163
+ if (IntegrationClass?.Definition) {
164
+ resolveScheduleConfig(IntegrationClass.Definition);
165
+ }
166
+ }
167
+
168
+ registerCredentialLogKeys(appDefinition, moduleDefinitions);
68
169
 
69
170
  // Degrade consistently: an invalid telemetry block must never take down a
70
171
  // router bundle that loads the app definition at module scope (telemetry is
@@ -88,16 +189,24 @@ function loadAppDefinition() {
88
189
  }
89
190
 
90
191
  return {
192
+ appDefinition,
91
193
  integrations,
194
+ entities,
195
+ entityTypeRegistry,
196
+ moduleDefinitions,
197
+ ownership,
92
198
  userConfig,
93
199
  adminScripts,
94
200
  reports,
95
201
  admin,
96
202
  telemetry,
97
203
  logging,
204
+ managementApi,
98
205
  };
99
206
  }
100
207
 
101
208
  module.exports = {
102
209
  loadAppDefinition,
210
+ setAppDefinition,
211
+ getPresetAppDefinition,
103
212
  };
@@ -6,17 +6,20 @@ const bodyParser = require('body-parser');
6
6
  const cors = require('cors');
7
7
  const Boom = require('@hapi/boom');
8
8
  const serverlessHttp = require('serverless-http');
9
+ const {
10
+ API_VERSION_HEADER,
11
+ isVersionedManagementApiPath,
12
+ toBoom,
13
+ toV2ErrorBody,
14
+ } = require('../management-api/error-response');
9
15
 
10
16
  const log = getLogger('frigg.http');
11
17
 
12
18
  const createApp = (applyMiddleware) => {
13
19
  const app = express();
14
20
 
15
- // Keep the bytes as received: webhook signatures (HubSpot, Slack, Stripe, Linear…) are computed over the exact
16
- // raw body, which re-serialising the parsed body does not reproduce. A reference to the buffer, not a copy.
17
- const keepRaw = (req, res, buf) => { req.rawBody = buf; };
18
- app.use(bodyParser.json({ limit: '10mb', verify: keepRaw }));
19
- app.use(bodyParser.urlencoded({ extended: true, verify: keepRaw }));
21
+ app.use(bodyParser.json({ limit: '10mb' }));
22
+ app.use(bodyParser.urlencoded({ extended: true }));
20
23
  app.use(
21
24
  cors({
22
25
  origin: '*',
@@ -29,11 +32,15 @@ const createApp = (applyMiddleware) => {
29
32
  if (applyMiddleware) applyMiddleware(app);
30
33
 
31
34
  // The express boundary: send the error response and log it one time.
35
+ // A Boom error keeps its status; other errors keep theirs only when they
36
+ // mark a 4xx safe to expose (body-parser's 400/413 via http-errors), so a
37
+ // malformed body is a 400, not a 500.
32
38
  app.use((err, req, res, next) => {
33
- const boomError = err.isBoom ? err : Boom.boomify(err);
39
+ const boomError = toBoom(err);
34
40
  const {
35
41
  output: { statusCode = 500 },
36
42
  } = boomError;
43
+ const versioned = isVersionedManagementApiPath(req.path);
37
44
 
38
45
  if (statusCode >= 500) {
39
46
  log.error('Request failed', {
@@ -42,7 +49,6 @@ const createApp = (applyMiddleware) => {
42
49
  invocation: summarizeExpressRequest(req),
43
50
  error: boomError,
44
51
  });
45
- res.status(statusCode).json({ error: 'Internal Server Error' });
46
52
  } else {
47
53
  // A client error needs no stack; the logger scrubs the reason.
48
54
  log.warn('Request rejected', {
@@ -50,8 +56,21 @@ const createApp = (applyMiddleware) => {
50
56
  statusCode,
51
57
  reason: boomError.message,
52
58
  });
53
- res.status(statusCode).json({ error: err.message });
54
59
  }
60
+
61
+ if (versioned) {
62
+ // Management API v2 / meta: { error: { code, message, details? } }.
63
+ if (req.path.startsWith('/api/v2')) {
64
+ res.set(API_VERSION_HEADER, '2');
65
+ }
66
+ return res.status(statusCode).json(toV2ErrorBody(boomError));
67
+ }
68
+
69
+ // v1 and other routes keep their historical body.
70
+ if (statusCode >= 500) {
71
+ return res.status(statusCode).json({ error: 'Internal Server Error' });
72
+ }
73
+ return res.status(statusCode).json({ error: err.message });
55
74
  });
56
75
 
57
76
  return app;
@@ -18,8 +18,9 @@ const {
18
18
  createModuleRepository,
19
19
  } = require('../modules/repositories/module-repository-factory');
20
20
  const {
21
- getModulesDefinitionFromIntegrationClasses,
22
- } = require('../integrations/utils/map-integration-dto');
21
+ getEntityTypeRegistry,
22
+ getEntityTypeDefinitions,
23
+ } = require('../modules/entity-type-registry');
23
24
  const { processNotFound } = require('../integrations/use-cases/process-errors');
24
25
 
25
26
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
@@ -61,30 +62,18 @@ const initializeRepositories = () => {
61
62
  return { processRepository, integrationRepository, moduleRepository };
62
63
  };
63
64
 
64
- const createModuleFactoryWithDefinitions = (
65
- moduleRepository,
66
- integrationClasses
67
- ) => {
68
- const moduleDefinitions =
69
- getModulesDefinitionFromIntegrationClasses(integrationClasses);
70
-
71
- return new ModuleFactory({
72
- moduleRepository,
73
- moduleDefinitions,
74
- });
75
- };
76
-
77
65
  const loadIntegrationForWebhook = async (integrationId) => {
78
66
  const { loadAppDefinition } = require('./app-definition-loader');
79
- const { integrations: integrationClasses } = loadAppDefinition();
67
+ const { integrations: integrationClasses, moduleDefinitions } =
68
+ loadAppDefinition();
80
69
 
81
70
  const { integrationRepository, moduleRepository } =
82
71
  initializeRepositories();
83
72
 
84
- const moduleFactory = createModuleFactoryWithDefinitions(
73
+ const moduleFactory = new ModuleFactory({
85
74
  moduleRepository,
86
- integrationClasses
87
- );
75
+ moduleDefinitions,
76
+ });
88
77
 
89
78
  const getIntegrationInstance = new GetIntegrationInstance({
90
79
  integrationRepository,
@@ -129,13 +118,23 @@ const integrationExists = async (integrationId) => {
129
118
  }
130
119
  };
131
120
 
132
- const loadIntegrationForProcess = async (processId, integrationClass) => {
121
+ const loadIntegrationForProcess = async (
122
+ processId,
123
+ integrationClass,
124
+ moduleDefinitions
125
+ ) => {
133
126
  const { processRepository, integrationRepository, moduleRepository } =
134
127
  initializeRepositories();
135
128
 
136
- const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
137
- integrationClass,
138
- ]);
129
+ const moduleFactory = new ModuleFactory({
130
+ moduleRepository,
131
+ moduleDefinitions:
132
+ moduleDefinitions ??
133
+ // No app definition handed in: this integration's own modules only.
134
+ getEntityTypeDefinitions(
135
+ getEntityTypeRegistry({ integrations: [integrationClass] })
136
+ ),
137
+ });
139
138
 
140
139
  const getIntegrationInstance = new GetIntegrationInstance({
141
140
  integrationRepository,
@@ -161,7 +160,13 @@ const loadIntegrationForProcess = async (processId, integrationClass) => {
161
160
  return instance;
162
161
  };
163
162
 
164
- const createQueueWorker = (integrationClass) => {
163
+ /**
164
+ * @param {Function} integrationClass
165
+ * @param {{moduleDefinitions?: Array<object>}} [options] - the app's offered
166
+ * module definitions (`loadAppDefinition().moduleDefinitions`). When omitted
167
+ * the worker hydrates with this integration's own modules only.
168
+ */
169
+ const createQueueWorker = (integrationClass, { moduleDefinitions } = {}) => {
165
170
  const integrationName = integrationClass.Definition.name;
166
171
 
167
172
  class QueueWorker extends Worker {
@@ -183,7 +188,8 @@ const createQueueWorker = (integrationClass) => {
183
188
  console.log(`[QueueWorker] hydrating by processId`, logCtx);
184
189
  integrationInstance = await loadIntegrationForProcess(
185
190
  params.data.processId,
186
- integrationClass
191
+ integrationClass,
192
+ moduleDefinitions
187
193
  );
188
194
  console.log(`[QueueWorker] hydrated`, {
189
195
  ...logCtx,
@@ -19,13 +19,9 @@ class IntegrationEventDispatcher {
19
19
  }
20
20
 
21
21
  async dispatchJob({ event, data, context, delivery }) {
22
- return this._dispatch(event, (instance, handler) => {
23
- const invoke = () => handler.call(instance, { data, context, delivery });
24
- // An integration that keeps run history records each queued event as a run (integration runtime).
25
- return typeof instance.runEvent === 'function'
26
- ? instance.runEvent(event, invoke)
27
- : invoke();
28
- });
22
+ return this._dispatch(event, (instance, handler) =>
23
+ handler.call(instance, { data, context, delivery })
24
+ );
29
25
  }
30
26
 
31
27
  /**
@@ -29,6 +29,19 @@ function secretsMatch(provided, expected) {
29
29
  return crypto.timingSafeEqual(providedHash, expectedHash);
30
30
  }
31
31
 
32
+ /**
33
+ * True when `provided` matches the configured ADMIN_API_KEY. False when no
34
+ * key is configured or none was provided. For callers that change a response
35
+ * for admins rather than reject everyone else (GET /api/meta).
36
+ * @param {string|undefined} provided
37
+ * @returns {boolean}
38
+ */
39
+ function isValidAdminApiKey(provided) {
40
+ const expectedKey = process.env.ADMIN_API_KEY;
41
+ if (!expectedKey || !provided) return false;
42
+ return secretsMatch(provided, expectedKey);
43
+ }
44
+
32
45
  /**
33
46
  * Validate admin API key from request header
34
47
  * @param {import('express').Request} req
@@ -70,4 +83,4 @@ function validateAdminApiKey(req, res, next) {
70
83
  next();
71
84
  }
72
85
 
73
- module.exports = { validateAdminApiKey };
86
+ module.exports = { validateAdminApiKey, isValidAdminApiKey };
@@ -1,15 +1,51 @@
1
- const { createIntegrationRouter } = require('@friggframework/core');
1
+ const express = require('express');
2
+ const {
3
+ createManagementApiRouter,
4
+ } = require('../../management-api/create-management-api-router');
5
+ const {
6
+ createExtensionRoutesRouter,
7
+ } = require('../../extensions/route-mounter');
2
8
  const { createAppHandler } = require('./../app-handler-helpers');
3
9
 
4
- const router = createIntegrationRouter();
10
+ // The Management API (ADR-053): v2 under /api/v2, the deprecated v1 at the
11
+ // unprefixed paths (or 410 when managementApi.v1 is false).
12
+ //
13
+ // Built on first access, not at require time (friggframework/frigg#545,
14
+ // c40eca49): a host that presets the app definition (setAppDefinition, as
15
+ // provider-netlify's function entry points do) can require this module
16
+ // before or after doing so.
17
+ let router;
18
+ let handler;
5
19
 
6
- router.route('/api/integrations/redirect/:appId').get((req, res) => {
7
- res.redirect(
8
- `${process.env.FRONTEND_URI}/redirect/${req.params.appId
9
- }?${new URLSearchParams(req.query)}`
10
- );
11
- });
20
+ function buildRouter() {
21
+ const built = express.Router();
22
+ built.use(createManagementApiRouter());
12
23
 
13
- const handler = createAppHandler('HTTP Event: Auth', router);
24
+ // The OAuth provider's redirect target. Unversioned: it is neither part
25
+ // of v1 nor v2, so it is never deprecated or disabled.
26
+ built.route('/api/integrations/redirect/:appId').get((req, res) => {
27
+ res.redirect(
28
+ `${process.env.FRONTEND_URI}/redirect/${req.params.appId
29
+ }?${new URLSearchParams(req.query)}`
30
+ );
31
+ });
14
32
 
15
- module.exports = { handler };
33
+ // Core extension routes (appDefinition.coreExtensions[].routes), each
34
+ // behind the admin API key.
35
+ built.use(createExtensionRoutesRouter());
36
+
37
+ return built;
38
+ }
39
+
40
+ module.exports = {
41
+ get router() {
42
+ if (!router) router = buildRouter();
43
+ return router;
44
+ },
45
+ get handler() {
46
+ if (!handler) {
47
+ handler = createAppHandler('HTTP Event: Auth', module.exports.router);
48
+ }
49
+ return handler;
50
+ },
51
+ };
@@ -2,9 +2,6 @@ const { Router } = require('express');
2
2
  const { createAppHandler } = require('./../app-handler-helpers');
3
3
  const { loadAppDefinition } = require('./../app-definition-loader');
4
4
  const { ModuleFactory } = require('../../modules/module-factory');
5
- const {
6
- getModulesDefinitionFromIntegrationClasses,
7
- } = require('../../integrations/utils/map-integration-dto');
8
5
  const {
9
6
  createModuleRepository,
10
7
  } = require('../../modules/repositories/module-repository-factory');
@@ -28,18 +25,25 @@ const {
28
25
  CheckIntegrationsHealthUseCase,
29
26
  } = require('../use-cases/check-integrations-health-use-case');
30
27
 
28
+ const {
29
+ createManagementApiMetaRouter,
30
+ createDocsRedirectRouter,
31
+ } = require('../../management-api/meta-router');
32
+
31
33
  const router = Router();
32
34
  const healthCheckRepository = createHealthCheckRepository({ prismaClient: prisma });
33
35
 
34
36
  // Load integrations and create factories just like auth router does
35
37
  // This verifies the system can properly load integrations
36
38
  let moduleFactory, integrationClasses;
39
+ let appDefinition = {};
37
40
  try {
38
41
  const appDef = loadAppDefinition();
42
+ appDefinition = appDef;
39
43
  integrationClasses = appDef.integrations || [];
40
44
 
41
45
  const moduleRepository = createModuleRepository();
42
- const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrationClasses);
46
+ const { moduleDefinitions } = appDef;
43
47
 
44
48
  moduleFactory = new ModuleFactory({
45
49
  moduleRepository,
@@ -85,6 +89,11 @@ const validateApiKey = (req, res, next) => {
85
89
  next();
86
90
  };
87
91
 
92
+ // Management API discovery (ADR-053 §4) and the /api/docs redirect to it are
93
+ // public and DB-free, so they are mounted ahead of the health API key check.
94
+ router.use(createManagementApiMetaRouter({ appDefinition }));
95
+ router.use(createDocsRedirectRouter());
96
+
88
97
  router.use(validateApiKey);
89
98
 
90
99
  // Helper to detect VPC configuration