@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.693.d8859c2.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 (261) hide show
  1. package/CLAUDE.md +0 -28
  2. package/README.md +0 -105
  3. package/application/commands/integration-commands.js +10 -20
  4. package/application/commands/scheduler-commands.js +7 -11
  5. package/application/commands/user-commands.js +5 -17
  6. package/application/index.js +2 -6
  7. package/core/Worker.js +7 -31
  8. package/core/create-handler.js +0 -9
  9. package/credential/repositories/credential-repository-documentdb.js +0 -34
  10. package/credential/repositories/credential-repository-interface.js +0 -15
  11. package/credential/repositories/credential-repository-mongo.js +0 -23
  12. package/credential/repositories/credential-repository-postgres.js +0 -28
  13. package/database/config.js +24 -48
  14. package/database/documentdb-utils.js +0 -2
  15. package/database/encryption/encryption-schema-registry.js +12 -93
  16. package/database/prisma.js +2 -4
  17. package/errors/index.js +4 -0
  18. package/errors/mapping-already-exists-error.js +14 -0
  19. package/generated/prisma-mongodb/edge.js +38 -33
  20. package/generated/prisma-mongodb/index-browser.js +35 -30
  21. package/generated/prisma-mongodb/index.d.ts +1880 -3191
  22. package/generated/prisma-mongodb/index.js +38 -33
  23. package/generated/prisma-mongodb/package.json +1 -1
  24. package/generated/prisma-mongodb/schema.prisma +50 -86
  25. package/generated/prisma-mongodb/wasm.js +38 -33
  26. package/generated/prisma-postgresql/edge.js +38 -33
  27. package/generated/prisma-postgresql/index-browser.js +35 -30
  28. package/generated/prisma-postgresql/index.d.ts +2050 -3390
  29. package/generated/prisma-postgresql/index.js +38 -33
  30. package/generated/prisma-postgresql/package.json +1 -1
  31. package/generated/prisma-postgresql/schema.prisma +48 -81
  32. package/generated/prisma-postgresql/wasm.js +38 -33
  33. package/handlers/WEBHOOKS.md +29 -0
  34. package/handlers/app-definition-loader.js +19 -128
  35. package/handlers/app-handler-helpers.js +3 -25
  36. package/handlers/backend-utils.js +25 -31
  37. package/handlers/middleware/admin-auth.js +1 -14
  38. package/handlers/routers/auth.js +10 -46
  39. package/handlers/routers/health.js +4 -13
  40. package/handlers/routers/integration-defined-routers.js +92 -144
  41. package/handlers/routers/integration-webhook-routers.js +64 -102
  42. package/handlers/workers/integration-defined-workers.js +2 -5
  43. package/index.js +3 -65
  44. package/infrastructure/scheduler/index.js +6 -19
  45. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  46. package/integrations/data-defined/README.md +169 -0
  47. package/integrations/data-defined/canonical.js +11 -0
  48. package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
  49. package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
  50. package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
  51. package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
  52. package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
  53. package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
  54. package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
  55. package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
  56. package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
  57. package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
  58. package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
  59. package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
  60. package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
  61. package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
  62. package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
  63. package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
  64. package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
  65. package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
  66. package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
  67. package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
  68. package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
  69. package/integrations/data-defined/compile.js +77 -0
  70. package/integrations/data-defined/config-form.js +110 -0
  71. package/integrations/data-defined/data-defined-integration.js +366 -0
  72. package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
  73. package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
  74. package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
  75. package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
  76. package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
  77. package/integrations/data-defined/examples/github-linear.definition.json +316 -0
  78. package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
  79. package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
  80. package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
  81. package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
  82. package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
  83. package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
  84. package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
  85. package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
  86. package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
  87. package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
  88. package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
  89. package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
  90. package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
  91. package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
  92. package/integrations/data-defined/expressions.js +225 -0
  93. package/integrations/data-defined/index.js +39 -0
  94. package/integrations/data-defined/interpreter.js +89 -0
  95. package/integrations/data-defined/manifests.js +125 -0
  96. package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
  97. package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
  98. package/integrations/data-defined/router.js +97 -0
  99. package/integrations/data-defined/runtime.js +208 -0
  100. package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
  101. package/integrations/data-defined/schema/installation.v0.1.schema.json +83 -0
  102. package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +848 -0
  103. package/integrations/data-defined/schema/records.md +98 -0
  104. package/integrations/data-defined/schema-check.js +58 -0
  105. package/integrations/data-defined/scripts/validate.js +66 -0
  106. package/integrations/data-defined/steps.js +89 -0
  107. package/integrations/data-defined/transforms.js +91 -0
  108. package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
  109. package/integrations/data-defined/validate.js +370 -0
  110. package/integrations/data-defined/versioning.js +161 -0
  111. package/integrations/data-defined/webhooks.js +119 -0
  112. package/integrations/index.js +0 -1
  113. package/integrations/integration-base.js +29 -0
  114. package/integrations/integration-router.js +101 -131
  115. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  116. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  117. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  118. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  119. package/integrations/repositories/integration-mapping-repository.js +29 -0
  120. package/integrations/repositories/integration-repository-documentdb.js +3 -35
  121. package/integrations/repositories/integration-repository-interface.js +1 -17
  122. package/integrations/repositories/integration-repository-mongo.js +77 -60
  123. package/integrations/repositories/integration-repository-postgres.js +76 -62
  124. package/integrations/repositories/process-repository-mongo.js +1 -1
  125. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  126. package/integrations/use-cases/create-integration.js +20 -55
  127. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  128. package/integrations/use-cases/index.js +1 -7
  129. package/integrations/utils/map-integration-dto.js +14 -43
  130. package/modules/index.js +0 -14
  131. package/modules/module-factory.js +0 -12
  132. package/modules/module.js +2 -53
  133. package/modules/repositories/module-repository-documentdb.js +6 -51
  134. package/modules/repositories/module-repository-interface.js +2 -19
  135. package/modules/repositories/module-repository-mongo.js +74 -67
  136. package/modules/repositories/module-repository-postgres.js +74 -66
  137. package/modules/repositories/module-repository.js +1 -2
  138. package/modules/requester/requester.js +5 -35
  139. package/modules/use-cases/get-entities-for-user.js +13 -26
  140. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  141. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  142. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  143. package/modules/use-cases/get-module.js +4 -10
  144. package/modules/use-cases/process-authorization-callback.js +19 -53
  145. package/modules/use-cases/refresh-entity-options.js +4 -10
  146. package/modules/use-cases/test-module-auth.js +4 -10
  147. package/package.json +6 -6
  148. package/prisma-mongodb/schema.prisma +50 -86
  149. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
  150. package/prisma-postgresql/schema.prisma +48 -81
  151. package/queues/index.js +0 -25
  152. package/types/index.d.ts +0 -2
  153. package/user/repositories/user-repository-documentdb.js +2 -27
  154. package/user/repositories/user-repository-interface.js +0 -18
  155. package/user/repositories/user-repository-mongo.js +0 -24
  156. package/user/repositories/user-repository-postgres.js +0 -18
  157. package/user/use-cases/authenticate-user.js +1 -7
  158. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  159. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  160. package/user/user.js +1 -64
  161. package/credential/use-cases/credential-operations-for-user.js +0 -202
  162. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  163. package/database/organization-where.js +0 -18
  164. package/database/unique-violation.js +0 -25
  165. package/extensions/bootstrap-runner.js +0 -43
  166. package/extensions/extension-loader.js +0 -187
  167. package/extensions/index.js +0 -41
  168. package/extensions/initialize-app.js +0 -123
  169. package/extensions/route-mounter.js +0 -103
  170. package/extensions/schema-composer.js +0 -188
  171. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  172. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  173. package/integrations/repositories/integration-not-found.js +0 -14
  174. package/integrations/schedule-config.js +0 -80
  175. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  176. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  177. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  178. package/management-api/create-management-api-router.js +0 -49
  179. package/management-api/error-response.js +0 -116
  180. package/management-api/management-api-config.js +0 -151
  181. package/management-api/meta-router.js +0 -76
  182. package/management-api/openapi/build-openapi.js +0 -144
  183. package/management-api/openapi/components.js +0 -415
  184. package/management-api/openapi/v1-operations.js +0 -83
  185. package/management-api/openapi/v2-operations.js +0 -264
  186. package/management-api/route-registry.js +0 -411
  187. package/management-api/test-support/http-client.js +0 -39
  188. package/management-api/use-cases/get-management-api-meta.js +0 -57
  189. package/management-api/v1-deprecation.js +0 -120
  190. package/management-api/v2/create-v2-router.js +0 -85
  191. package/management-api/v2/dependencies.js +0 -281
  192. package/management-api/v2/dto.js +0 -143
  193. package/management-api/v2/handlers/authorization.js +0 -94
  194. package/management-api/v2/handlers/credentials.js +0 -33
  195. package/management-api/v2/handlers/entities.js +0 -77
  196. package/management-api/v2/handlers/integrations.js +0 -128
  197. package/management-api/v2/handlers/proxy.js +0 -34
  198. package/management-api/v2/request.js +0 -85
  199. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  200. package/modules/domain/authorization-session.js +0 -144
  201. package/modules/entity-scope.js +0 -15
  202. package/modules/entity-type-registry.js +0 -311
  203. package/modules/proxy/proxy-bases.js +0 -169
  204. package/modules/proxy/proxy-errors.js +0 -31
  205. package/modules/proxy/proxy-path.js +0 -183
  206. package/modules/proxy/proxy-policy.js +0 -277
  207. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  208. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  209. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  210. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  211. package/modules/repositories/entity-not-found.js +0 -18
  212. package/modules/requester/proxy-request.js +0 -245
  213. package/modules/scope-config.js +0 -192
  214. package/modules/scope-policy.js +0 -112
  215. package/modules/scope-resolution.js +0 -288
  216. package/modules/scope-visibility.js +0 -247
  217. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  218. package/modules/tests/doubles/ownership-matching.js +0 -18
  219. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  220. package/modules/use-cases/create-entity-from-credential.js +0 -113
  221. package/modules/use-cases/entity-operations-for-user.js +0 -162
  222. package/modules/use-cases/entity-types.js +0 -139
  223. package/modules/use-cases/execute-entity-proxy-request.js +0 -331
  224. package/modules/use-cases/get-authorization-requirements.js +0 -100
  225. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  226. package/modules/use-cases/manage-access.js +0 -21
  227. package/modules/use-cases/multi-step-authorization.js +0 -329
  228. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  229. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  230. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  231. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  232. package/providers/index.js +0 -23
  233. package/providers/resolve-provider.js +0 -93
  234. package/providers/runtime-detection.js +0 -124
  235. package/queues/providers/index.js +0 -11
  236. package/queues/providers/netlify-background-provider.js +0 -150
  237. package/queues/providers/qstash-queue-provider.js +0 -219
  238. package/queues/providers/sqs-queue-provider.js +0 -70
  239. package/queues/queue-client-interface.js +0 -55
  240. package/queues/queue-message-auth.js +0 -193
  241. package/queues/queue-provider-factory.js +0 -101
  242. package/queues/queue-provider.js +0 -65
  243. package/queues/sqs-queue-client.js +0 -81
  244. package/types/modules/index.d.ts +0 -214
  245. package/types/user/index.d.ts +0 -134
  246. package/user/adopter-jwt-config.js +0 -177
  247. package/user/adopter-jwt-errors.js +0 -59
  248. package/user/adopter-token-verifier.js +0 -122
  249. package/user/app-user-helpers.js +0 -97
  250. package/user/create-user-authentication.js +0 -64
  251. package/user/membership.js +0 -51
  252. package/user/repositories/organization-membership-record.js +0 -19
  253. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  254. package/user/repositories/organization-membership-repository-factory.js +0 -42
  255. package/user/repositories/organization-membership-repository-interface.js +0 -57
  256. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  257. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  258. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  259. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  260. package/user/use-cases/ensure-organization-membership.js +0 -92
  261. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -1,169 +1,117 @@
1
1
  const { createAppHandler } = require('./../app-handler-helpers');
2
- const { loadAppDefinition } = require('../app-definition-loader');
2
+ const {
3
+ loadAppDefinition,
4
+ } = require('../app-definition-loader');
3
5
  const express = require('express');
4
6
  const { Router } = express;
5
7
  const { loadRouterFromObject } = require('../backend-utils');
6
8
  const { getExtensionRoutes } = require('../../integrations/extension');
7
9
 
10
+ const handlers = {};
11
+ const { integrations: integrationClasses } = loadAppDefinition();
12
+
8
13
  const routeKey = (method, path) => `${(method || 'ANY').toUpperCase()} ${path}`;
9
14
 
10
15
  // Serverless function keys must be alphanumeric; binding keys are developer-chosen.
11
16
  const sanitizeBindingKey = (name) => String(name).replace(/[^A-Za-z0-9]/g, '');
12
17
 
13
- /**
14
- * Builds the Express routers for every integration's Definition.routes and
15
- * extension bindings. Pure: no handler wrapping, so the AWS handlers below
16
- * and other hosts (provider-netlify's integration-routes function) mount the
17
- * very same routers.
18
- *
19
- * @param {Array<Function>} integrationClasses
20
- * @returns {Array<{ key: string, eventName: string, router: import('express').Router, useDatabase: boolean }>}
21
- * One entry per integration, then one per extension binding (key
22
- * `${name}__${binding}`), in declaration order.
23
- */
24
- function buildIntegrationRouters(integrationClasses) {
25
- const entries = [];
26
- const keys = new Set();
27
- //todo: this should be in a use case class
28
- for (const IntegrationClass of integrationClasses) {
29
- const router = Router();
30
- const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
31
- // Track (method, path) tuples to fail fast on conflicts between Definition.routes,
32
- // extension routes, or two extensions claiming the same path.
33
- const claimedRoutes = new Map();
34
- const claim = (method, path, source) => {
35
- const key = routeKey(method, path);
36
- if (claimedRoutes.has(key)) {
37
- const prev = claimedRoutes.get(key);
38
- throw new Error(
39
- `Integration "${IntegrationClass.Definition.name}" route conflict: ` +
40
- `${key} declared by ${prev} and ${source}`
41
- );
42
- }
43
- claimedRoutes.set(key, source);
44
- };
45
-
46
- console.log(
47
- `\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`
48
- );
49
-
50
- const routes = IntegrationClass.Definition.routes || [];
51
- for (const routeDef of routes) {
52
- if (typeof routeDef === 'function') {
53
- router.use(basePath, routeDef(IntegrationClass));
54
- console.log(`│ ANY ${basePath}/* (function handler)`);
55
- } else if (routeDef instanceof express.Router) {
56
- router.use(basePath, routeDef);
57
- console.log(`│ ANY ${basePath}/* (express router)`);
58
- } else if (typeof routeDef === 'object') {
59
- claim(routeDef.method, routeDef.path, 'Definition.routes');
60
- router.use(
61
- basePath,
62
- loadRouterFromObject(IntegrationClass, routeDef)
63
- );
64
- const method = (routeDef.method || 'ANY').toUpperCase();
65
- const fullPath = `${basePath}${routeDef.path}`;
66
- console.log(`│ ${method} ${fullPath}`);
67
- }
68
- }
69
-
70
- // Each extension binding gets its own namespaced handler (/{bindingName}),
71
- // so two modules' extensions can share a path like /webhooks without colliding.
72
- const bindingGroups = new Map();
73
- for (const extRoute of getExtensionRoutes(IntegrationClass)) {
74
- const namespacedPath = `/${extRoute.bindingName}${extRoute.path}`;
75
- claim(
76
- extRoute.method,
77
- namespacedPath,
78
- `extension "${extRoute.extensionName}" (binding "${extRoute.bindingName}")`
79
- );
80
- if (!bindingGroups.has(extRoute.bindingName)) {
81
- bindingGroups.set(extRoute.bindingName, {
82
- router: Router(),
83
- useDatabase: extRoute.useDatabase,
84
- });
85
- }
86
- const group = bindingGroups.get(extRoute.bindingName);
87
- group.router.use(
88
- `${basePath}/${extRoute.bindingName}`,
89
- loadRouterFromObject(IntegrationClass, extRoute)
90
- );
91
- console.log(
92
- `│ ${extRoute.method.toUpperCase()} ${basePath}/${
93
- extRoute.bindingName
94
- }${extRoute.path} (extension: ${
95
- extRoute.extensionName
96
- }, useDatabase: ${extRoute.useDatabase})`
18
+ //todo: this should be in a use case class
19
+ for (const IntegrationClass of integrationClasses) {
20
+ const router = Router();
21
+ const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
22
+ // Track (method, path) tuples to fail fast on conflicts between Definition.routes,
23
+ // extension routes, or two extensions claiming the same path.
24
+ const claimedRoutes = new Map();
25
+ const claim = (method, path, source) => {
26
+ const key = routeKey(method, path);
27
+ if (claimedRoutes.has(key)) {
28
+ const prev = claimedRoutes.get(key);
29
+ throw new Error(
30
+ `Integration "${IntegrationClass.Definition.name}" route conflict: ` +
31
+ `${key} declared by ${prev} and ${source}`
97
32
  );
98
33
  }
99
- console.log('│');
34
+ claimedRoutes.set(key, source);
35
+ };
100
36
 
101
- entries.push({
102
- key: `${IntegrationClass.Definition.name}`,
103
- eventName: `HTTP Event: ${IntegrationClass.Definition.name}`,
104
- router,
105
- useDatabase: true,
106
- });
107
- keys.add(`${IntegrationClass.Definition.name}`);
37
+ console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
108
38
 
109
- for (const [bindingName, group] of bindingGroups) {
110
- // Wire contract: integration-builder.js (devtools) derives the identical
111
- // function key for the serverless config. Keep both in sync.
112
- const fnKey = `${
113
- IntegrationClass.Definition.name
114
- }__${sanitizeBindingKey(bindingName)}`;
115
- // Distinct binding keys can sanitize to the same fnKey — fail loud rather than overwrite.
116
- if (keys.has(fnKey)) {
117
- throw new Error(
118
- `Integration "${IntegrationClass.Definition.name}" extension handler conflict: ` +
119
- `binding "${bindingName}" sanitizes to "${fnKey}", which is already taken. ` +
120
- `Use binding keys that are distinct after stripping non-alphanumeric characters.`
121
- );
122
- }
123
- entries.push({
124
- key: fnKey,
125
- eventName: `HTTP Event: ${IntegrationClass.Definition.name} extension ${bindingName}`,
126
- router: group.router,
127
- useDatabase: group.useDatabase,
128
- });
129
- keys.add(fnKey);
39
+ const routes = IntegrationClass.Definition.routes || [];
40
+ for (const routeDef of routes) {
41
+ if (typeof routeDef === 'function') {
42
+ router.use(basePath, routeDef(IntegrationClass));
43
+ console.log(`│ ANY ${basePath}/* (function handler)`);
44
+ } else if (routeDef instanceof express.Router) {
45
+ router.use(basePath, routeDef);
46
+ console.log(`│ ANY ${basePath}/* (express router)`);
47
+ } else if (typeof routeDef === 'object') {
48
+ claim(routeDef.method, routeDef.path, 'Definition.routes');
49
+ router.use(
50
+ basePath,
51
+ loadRouterFromObject(IntegrationClass, routeDef)
52
+ );
53
+ const method = (routeDef.method || 'ANY').toUpperCase();
54
+ const fullPath = `${basePath}${routeDef.path}`;
55
+ console.log(`│ ${method} ${fullPath}`);
130
56
  }
131
57
  }
132
- return entries;
133
- }
134
58
 
135
- /**
136
- * One router serving every integration-defined route and extension binding,
137
- * for hosts that run them all in one function (provider-netlify).
138
- *
139
- * @param {Array<Function>} [integrationClasses] - defaults to the app's.
140
- * @returns {import('express').Router}
141
- */
142
- function createIntegrationDefinedRouter(integrationClasses) {
143
- const classes = integrationClasses || loadAppDefinition().integrations;
144
- const combined = Router();
145
- for (const entry of buildIntegrationRouters(classes)) {
146
- combined.use(entry.router);
59
+ // Each extension binding gets its own namespaced handler (/{bindingName}),
60
+ // so two modules' extensions can share a path like /webhooks without colliding.
61
+ const bindingGroups = new Map();
62
+ for (const extRoute of getExtensionRoutes(IntegrationClass)) {
63
+ const namespacedPath = `/${extRoute.bindingName}${extRoute.path}`;
64
+ claim(
65
+ extRoute.method,
66
+ namespacedPath,
67
+ `extension "${extRoute.extensionName}" (binding "${extRoute.bindingName}")`
68
+ );
69
+ if (!bindingGroups.has(extRoute.bindingName)) {
70
+ bindingGroups.set(extRoute.bindingName, {
71
+ router: Router(),
72
+ useDatabase: extRoute.useDatabase,
73
+ });
74
+ }
75
+ const group = bindingGroups.get(extRoute.bindingName);
76
+ group.router.use(
77
+ `${basePath}/${extRoute.bindingName}`,
78
+ loadRouterFromObject(IntegrationClass, extRoute)
79
+ );
80
+ console.log(
81
+ `│ ${extRoute.method.toUpperCase()} ${basePath}/${extRoute.bindingName}${extRoute.path} (extension: ${extRoute.extensionName}, useDatabase: ${extRoute.useDatabase})`
82
+ );
147
83
  }
148
- return combined;
149
- }
84
+ console.log('│');
150
85
 
151
- // The AWS handlers (one Lambda per integration and per extension binding,
152
- // see devtools integration-builder). Built at require time on purpose: a
153
- // route conflict fails the cold start, not the first request.
154
- const handlers = {};
155
- for (const entry of buildIntegrationRouters(loadAppDefinition().integrations)) {
156
- handlers[entry.key] = {
86
+ handlers[`${IntegrationClass.Definition.name}`] = {
157
87
  handler: createAppHandler(
158
- entry.eventName,
159
- entry.router,
160
- entry.useDatabase
88
+ `HTTP Event: ${IntegrationClass.Definition.name}`,
89
+ router
161
90
  ),
162
91
  };
92
+
93
+ for (const [bindingName, group] of bindingGroups) {
94
+ // Wire contract: integration-builder.js (devtools) derives the identical
95
+ // function key for the serverless config. Keep both in sync.
96
+ const fnKey = `${IntegrationClass.Definition.name}__${sanitizeBindingKey(
97
+ bindingName
98
+ )}`;
99
+ // Distinct binding keys can sanitize to the same fnKey — fail loud rather than overwrite.
100
+ if (Object.prototype.hasOwnProperty.call(handlers, fnKey)) {
101
+ throw new Error(
102
+ `Integration "${IntegrationClass.Definition.name}" extension handler conflict: ` +
103
+ `binding "${bindingName}" sanitizes to "${fnKey}", which is already taken. ` +
104
+ `Use binding keys that are distinct after stripping non-alphanumeric characters.`
105
+ );
106
+ }
107
+ handlers[fnKey] = {
108
+ handler: createAppHandler(
109
+ `HTTP Event: ${IntegrationClass.Definition.name} extension ${bindingName}`,
110
+ group.router,
111
+ group.useDatabase
112
+ ),
113
+ };
114
+ }
163
115
  }
164
116
 
165
- module.exports = {
166
- handlers,
167
- buildIntegrationRouters,
168
- createIntegrationDefinedRouter,
169
- };
117
+ module.exports = { handlers };
@@ -6,120 +6,82 @@ const {
6
6
  IntegrationEventDispatcher,
7
7
  } = require('../integration-event-dispatcher');
8
8
 
9
+ const handlers = {};
10
+ const { integrations: integrationClasses } = loadAppDefinition();
11
+
9
12
  // `webhooks.received` is counted at the DB-connected ON_WEBHOOK dispatch (see
10
13
  // usage-rollup-subscriber), not in this DB-free receipt handler.
11
14
 
12
- /**
13
- * Builds the webhook receiver router for every integration with webhooks
14
- * enabled. Pure: no handler wrapping, so the AWS handlers below and other
15
- * hosts (provider-netlify's webhooks function) mount the same routers.
16
- *
17
- * @param {Array<Function>} integrationClasses
18
- * @returns {Array<{ key: string, eventName: string, router: import('express').Router }>}
19
- */
20
- function buildWebhookRouters(integrationClasses) {
21
- const entries = [];
22
- for (const IntegrationClass of integrationClasses) {
23
- const webhookConfig = IntegrationClass.Definition.webhooks;
24
-
25
- // Skip if webhooks not enabled
26
- if (
27
- !webhookConfig ||
28
- (typeof webhookConfig === 'object' && !webhookConfig.enabled)
29
- ) {
30
- continue;
31
- }
32
-
33
- const router = Router();
34
- const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
15
+ for (const IntegrationClass of integrationClasses) {
16
+ const webhookConfig = IntegrationClass.Definition.webhooks;
35
17
 
36
- console.log(
37
- `\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`
38
- );
18
+ // Skip if webhooks not enabled
19
+ if (
20
+ !webhookConfig ||
21
+ (typeof webhookConfig === 'object' && !webhookConfig.enabled)
22
+ ) {
23
+ continue;
24
+ }
39
25
 
40
- // General webhook route (no integration ID)
41
- router.post(basePath, async (req, res, next) => {
42
- try {
43
- const integrationInstance = new IntegrationClass();
44
- const dispatcher = new IntegrationEventDispatcher(
45
- integrationInstance
46
- );
47
- await dispatcher.dispatchHttp({
48
- event: 'WEBHOOK_RECEIVED',
49
- req,
50
- res,
51
- next,
52
- });
53
- } catch (error) {
54
- next(error);
55
- }
56
- });
57
- console.log(`│ POST ${basePath}`);
26
+ const router = Router();
27
+ const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
58
28
 
59
- // Integration-specific webhook route (with integration ID)
60
- router.post(`${basePath}/:integrationId`, async (req, res, next) => {
61
- try {
62
- const integrationInstance = new IntegrationClass();
63
- const dispatcher = new IntegrationEventDispatcher(
64
- integrationInstance
65
- );
66
- // Logs only: the id comes from an unauthenticated URL, so it must
67
- // not reach the telemetry context that drives usage attribution.
68
- await runInContext(
69
- {
70
- [LOGGER_SCOPE_KEY]: {
71
- integrationId: req.params.integrationId,
72
- },
73
- },
74
- () =>
75
- dispatcher.dispatchHttp({
76
- event: 'WEBHOOK_RECEIVED',
77
- req,
78
- res,
79
- next,
80
- })
81
- );
82
- } catch (error) {
83
- next(error);
84
- }
85
- });
86
- console.log(`│ POST ${basePath}/:integrationId`);
87
- console.log('│');
29
+ console.log(
30
+ `\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`
31
+ );
88
32
 
89
- entries.push({
90
- key: `${IntegrationClass.Definition.name}Webhook`,
91
- eventName: `HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
92
- router,
93
- });
94
- }
95
- return entries;
96
- }
33
+ // General webhook route (no integration ID)
34
+ router.post(basePath, async (req, res, next) => {
35
+ try {
36
+ const integrationInstance = new IntegrationClass();
37
+ const dispatcher = new IntegrationEventDispatcher(
38
+ integrationInstance
39
+ );
40
+ await dispatcher.dispatchHttp({
41
+ event: 'WEBHOOK_RECEIVED',
42
+ req,
43
+ res,
44
+ next,
45
+ });
46
+ } catch (error) {
47
+ next(error);
48
+ }
49
+ });
50
+ console.log(`│ POST ${basePath}`);
97
51
 
98
- /**
99
- * One router serving every integration's webhook routes, for hosts that run
100
- * them all in one function (provider-netlify).
101
- *
102
- * @param {Array<Function>} [integrationClasses] - defaults to the app's.
103
- * @returns {import('express').Router}
104
- */
105
- function createWebhookRouter(integrationClasses) {
106
- const classes = integrationClasses || loadAppDefinition().integrations;
107
- const combined = Router();
108
- for (const entry of buildWebhookRouters(classes)) {
109
- combined.use(entry.router);
110
- }
111
- return combined;
112
- }
52
+ // Integration-specific webhook route (with integration ID)
53
+ router.post(`${basePath}/:integrationId`, async (req, res, next) => {
54
+ try {
55
+ const integrationInstance = new IntegrationClass();
56
+ const dispatcher = new IntegrationEventDispatcher(
57
+ integrationInstance
58
+ );
59
+ // Logs only: the id comes from an unauthenticated URL, so it must
60
+ // not reach the telemetry context that drives usage attribution.
61
+ await runInContext(
62
+ { [LOGGER_SCOPE_KEY]: { integrationId: req.params.integrationId } },
63
+ () =>
64
+ dispatcher.dispatchHttp({
65
+ event: 'WEBHOOK_RECEIVED',
66
+ req,
67
+ res,
68
+ next,
69
+ })
70
+ );
71
+ } catch (error) {
72
+ next(error);
73
+ }
74
+ });
75
+ console.log(`│ POST ${basePath}/:integrationId`);
76
+ console.log('│');
113
77
 
114
- const handlers = {};
115
- for (const entry of buildWebhookRouters(loadAppDefinition().integrations)) {
116
- handlers[entry.key] = {
78
+ handlers[`${IntegrationClass.Definition.name}Webhook`] = {
117
79
  handler: createAppHandler(
118
- entry.eventName,
119
- entry.router,
80
+ `HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
81
+ router,
120
82
  false // shouldUseDatabase = false
121
83
  ),
122
84
  };
123
85
  }
124
86
 
125
- module.exports = { handlers, buildWebhookRouters, createWebhookRouter };
87
+ module.exports = { handlers };
@@ -10,13 +10,10 @@ const { createQueueWorker } = require('../backend-utils');
10
10
  // const { getExtensionWorkers } = require('../../integrations/extension');
11
11
 
12
12
  const handlers = {};
13
- const { integrations: integrationClasses, moduleDefinitions } =
14
- loadAppDefinition();
13
+ const { integrations: integrationClasses } = loadAppDefinition();
15
14
 
16
15
  integrationClasses.forEach((IntegrationClass) => {
17
- const defaultQueueWorker = createQueueWorker(IntegrationClass, {
18
- moduleDefinitions,
19
- });
16
+ const defaultQueueWorker = createQueueWorker(IntegrationClass);
20
17
 
21
18
  handlers[`${IntegrationClass.Definition.name}`] = {
22
19
  queueWorker: createHandler({
package/index.js CHANGED
@@ -25,14 +25,6 @@ const {
25
25
  UserRepositoryMongo,
26
26
  UserRepositoryPostgres,
27
27
  } = require('./user/repositories/user-repository-factory');
28
- const {
29
- createOrganizationMembershipRepository,
30
- OrganizationMembershipRepositoryMongo,
31
- OrganizationMembershipRepositoryPostgres,
32
- } = require('./user/repositories/organization-membership-repository-factory');
33
- const {
34
- EnsureOrganizationMembership,
35
- } = require('./user/use-cases/ensure-organization-membership');
36
28
  const {
37
29
  GetUserFromXFriggHeaders,
38
30
  } = require('./user/use-cases/get-user-from-x-frigg-headers');
@@ -65,6 +57,7 @@ const {
65
57
  HaltError,
66
58
  RequiredPropertyError,
67
59
  ParameterTypeError,
60
+ MappingAlreadyExistsError,
68
61
  } = require('./errors/index');
69
62
  const {
70
63
  IntegrationBase,
@@ -107,36 +100,11 @@ const {
107
100
  Requester,
108
101
  ModuleConstants,
109
102
  ModuleFactory,
110
- getEntityTypeRegistry,
111
- EntityRegistryError,
112
- resolveScope,
113
- listBindingScopes,
114
- OwnershipConfigError,
115
103
  } = require('./modules/index');
116
- const {
117
- ManagementApiConfigError,
118
- } = require('./management-api/management-api-config');
119
104
  const application = require('./application');
120
105
  const utils = require('./utils');
121
106
 
122
- const {
123
- QueuerUtil,
124
- QueueProvider,
125
- QueueClientInterface,
126
- createQueueProvider,
127
- QUEUE_PROVIDERS,
128
- } = require('./queues');
129
- const {
130
- resolveProvider,
131
- determineProviderName,
132
- providerPackageName,
133
- KNOWN_PROVIDERS,
134
- isDeployedRuntime,
135
- } = require('./providers');
136
-
137
- // App-level core extensions (appDefinition.coreExtensions): Prisma models, encrypted
138
- // fields, admin routes and bootstrap hooks (friggframework/frigg#545).
139
- const extensions = require('./extensions');
107
+ const { QueuerUtil } = require('./queues');
140
108
 
141
109
  module.exports = {
142
110
  // assertions
@@ -163,10 +131,6 @@ module.exports = {
163
131
  createUserRepository,
164
132
  UserRepositoryMongo,
165
133
  UserRepositoryPostgres,
166
- createOrganizationMembershipRepository,
167
- OrganizationMembershipRepositoryMongo,
168
- OrganizationMembershipRepositoryPostgres,
169
- EnsureOrganizationMembership,
170
134
  GetUserFromXFriggHeaders,
171
135
  GetUserFromAdopterJwt,
172
136
  AuthenticateUser,
@@ -181,13 +145,13 @@ module.exports = {
181
145
  HaltError,
182
146
  RequiredPropertyError,
183
147
  ParameterTypeError,
148
+ MappingAlreadyExistsError,
184
149
 
185
150
  // integrations
186
151
  IntegrationBase,
187
152
  Options,
188
153
  checkRequiredParams,
189
154
  createIntegrationRouter,
190
- // Deprecated (ADR-059): use getEntityTypeRegistry.
191
155
  getModulesDefinitionFromIntegrationClasses,
192
156
  LoadIntegrationContextUseCase,
193
157
  CreateProcess,
@@ -244,34 +208,8 @@ module.exports = {
244
208
  Requester,
245
209
  ModuleConstants,
246
210
  ModuleFactory,
247
- getEntityTypeRegistry,
248
- EntityRegistryError,
249
- resolveScope,
250
- listBindingScopes,
251
- OwnershipConfigError,
252
- ManagementApiConfigError,
253
211
  // queues
254
212
  QueuerUtil,
255
- QueueProvider,
256
- QueueClientInterface,
257
- createQueueProvider,
258
- QUEUE_PROVIDERS,
259
-
260
- // provider plugins (appDefinition.provider)
261
- resolveProvider,
262
- determineProviderName,
263
- providerPackageName,
264
- KNOWN_PROVIDERS,
265
- isDeployedRuntime,
266
-
267
- // app-level extensions
268
- extensions,
269
- loadExtensions: extensions.loadExtensions,
270
- composeSchemas: extensions.composeSchemas,
271
- mountExtensionRoutes: extensions.mountExtensionRoutes,
272
- createExtensionRoutesRouter: extensions.createExtensionRoutesRouter,
273
- runExtensionBootstraps: extensions.runExtensionBootstraps,
274
- initializeApp: extensions.initializeApp,
275
213
 
276
214
  // utils
277
215
  ...utils,
@@ -5,19 +5,13 @@
5
5
  * Follows hexagonal architecture with interface + adapters pattern.
6
6
  *
7
7
  * Providers:
8
- * - eventbridge: AWS EventBridge Scheduler (production on AWS)
9
- * - netlify: poll-and-dispatch from a Netlify scheduled function
8
+ * - eventbridge: AWS EventBridge Scheduler (production)
10
9
  * - mock: In-memory mock scheduler (local development)
11
- *
12
- * Adapters are lazy getters (friggframework/frigg#545, 1176a004): the
13
- * EventBridge adapter requires @aws-sdk/client-scheduler, which is not a
14
- * core dependency, and core/index -> application -> scheduler-commands ->
15
- * this barrel runs on every require('@friggframework/core'). An eager
16
- * require crashed any deployment without that SDK (e.g. a pruned Netlify
17
- * function bundle).
18
10
  */
19
11
 
20
12
  const { SchedulerServiceInterface } = require('./scheduler-service-interface');
13
+ const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
14
+ const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
21
15
  const {
22
16
  createSchedulerService,
23
17
  SCHEDULER_PROVIDERS,
@@ -28,16 +22,9 @@ module.exports = {
28
22
  // Interface (Port)
29
23
  SchedulerServiceInterface,
30
24
 
31
- // Adapters (loaded on access)
32
- get EventBridgeSchedulerAdapter() {
33
- return require('./eventbridge-scheduler-adapter').EventBridgeSchedulerAdapter;
34
- },
35
- get MockSchedulerAdapter() {
36
- return require('./mock-scheduler-adapter').MockSchedulerAdapter;
37
- },
38
- get NetlifySchedulerAdapter() {
39
- return require('./netlify-scheduler-adapter').NetlifySchedulerAdapter;
40
- },
25
+ // Adapters
26
+ EventBridgeSchedulerAdapter,
27
+ MockSchedulerAdapter,
41
28
 
42
29
  // Factory
43
30
  createSchedulerService,