@friggframework/core 2.0.0--canary.686.df38e8e.0 → 2.0.0--canary.710.9045bbc.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 (217) hide show
  1. package/CHANGELOG.md +0 -16
  2. package/CLAUDE.md +41 -29
  3. package/README.md +0 -105
  4. package/application/commands/README.md +17 -11
  5. package/application/commands/entity-commands.js +18 -60
  6. package/application/commands/integration-commands.js +73 -20
  7. package/application/commands/scheduler-commands.js +7 -11
  8. package/application/commands/user-commands.js +5 -17
  9. package/application/index.js +2 -6
  10. package/core/Worker.js +7 -31
  11. package/core/create-handler.js +0 -9
  12. package/credential/repositories/credential-repository-documentdb.js +0 -34
  13. package/credential/repositories/credential-repository-interface.js +0 -15
  14. package/credential/repositories/credential-repository-mongo.js +0 -23
  15. package/credential/repositories/credential-repository-postgres.js +0 -28
  16. package/database/config.js +24 -48
  17. package/database/documentdb-encryption-service.js +22 -9
  18. package/database/documentdb-utils.js +0 -2
  19. package/database/encryption/README.md +17 -81
  20. package/database/encryption/documentdb-encryption-service.md +1 -78
  21. package/database/encryption/encryption-schema-registry.js +46 -123
  22. package/database/encryption/field-encryption-service.js +5 -69
  23. package/database/encryption/integration-mapping-encryption.js +1 -1
  24. package/database/prisma.js +38 -15
  25. package/database/use-cases/check-encryption-health-use-case.js +52 -43
  26. package/errors/index.js +4 -0
  27. package/errors/mapping-already-exists-error.js +14 -0
  28. package/generated/prisma-mongodb/edge.js +4 -43
  29. package/generated/prisma-mongodb/index-browser.js +1 -40
  30. package/generated/prisma-mongodb/index.d.ts +111 -4739
  31. package/generated/prisma-mongodb/index.js +4 -43
  32. package/generated/prisma-mongodb/package.json +1 -1
  33. package/generated/prisma-mongodb/schema.prisma +0 -92
  34. package/generated/prisma-mongodb/wasm.js +4 -43
  35. package/generated/prisma-postgresql/edge.js +4 -43
  36. package/generated/prisma-postgresql/index-browser.js +1 -40
  37. package/generated/prisma-postgresql/index.d.ts +33 -4966
  38. package/generated/prisma-postgresql/index.js +4 -43
  39. package/generated/prisma-postgresql/package.json +1 -1
  40. package/generated/prisma-postgresql/schema.prisma +0 -87
  41. package/generated/prisma-postgresql/wasm.js +4 -43
  42. package/handlers/WEBHOOKS.md +29 -0
  43. package/handlers/app-definition-loader.js +19 -121
  44. package/handlers/app-handler-helpers.js +3 -25
  45. package/handlers/backend-utils.js +25 -31
  46. package/handlers/middleware/admin-auth.js +1 -14
  47. package/handlers/routers/auth.js +10 -46
  48. package/handlers/routers/health.js +4 -13
  49. package/handlers/routers/integration-defined-routers.js +92 -144
  50. package/handlers/routers/integration-webhook-routers.js +64 -102
  51. package/handlers/workers/integration-defined-workers.js +2 -5
  52. package/index.js +3 -65
  53. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
  54. package/infrastructure/scheduler/index.js +6 -19
  55. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  56. package/integrations/index.js +0 -1
  57. package/integrations/integration-base.js +29 -0
  58. package/integrations/integration-router.js +96 -131
  59. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  60. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  61. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  62. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  63. package/integrations/repositories/integration-mapping-repository.js +29 -0
  64. package/integrations/repositories/integration-repository-documentdb.js +35 -35
  65. package/integrations/repositories/integration-repository-interface.js +31 -17
  66. package/integrations/repositories/integration-repository-mongo.js +129 -60
  67. package/integrations/repositories/integration-repository-postgres.js +134 -62
  68. package/integrations/repositories/process-repository-mongo.js +1 -1
  69. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  70. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  71. package/integrations/use-cases/create-integration.js +75 -72
  72. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  73. package/integrations/use-cases/index.js +1 -7
  74. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  75. package/integrations/utils/assert-entity-ids-input.js +42 -0
  76. package/integrations/utils/map-integration-dto.js +14 -43
  77. package/jest-setup.js +13 -2
  78. package/jest-teardown.js +5 -2
  79. package/jest.config.js +4 -0
  80. package/jest.db.config.js +23 -0
  81. package/modules/index.js +0 -14
  82. package/modules/module-factory.js +0 -12
  83. package/modules/module.js +2 -53
  84. package/modules/repositories/module-repository-documentdb.js +6 -51
  85. package/modules/repositories/module-repository-interface.js +2 -19
  86. package/modules/repositories/module-repository-mongo.js +74 -67
  87. package/modules/repositories/module-repository-postgres.js +74 -66
  88. package/modules/repositories/module-repository.js +1 -2
  89. package/modules/requester/requester.js +2 -27
  90. package/modules/use-cases/get-entities-for-user.js +13 -26
  91. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  92. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  93. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  94. package/modules/use-cases/get-module.js +4 -10
  95. package/modules/use-cases/process-authorization-callback.js +19 -53
  96. package/modules/use-cases/refresh-entity-options.js +4 -10
  97. package/modules/use-cases/test-module-auth.js +4 -10
  98. package/modules/utils/map-module-dto.js +5 -3
  99. package/modules/utils/to-credential-id.js +14 -0
  100. package/package.json +7 -6
  101. package/prisma-mongodb/schema.prisma +0 -92
  102. package/prisma-postgresql/schema.prisma +0 -87
  103. package/queues/index.js +0 -25
  104. package/queues/queuer-util.js +6 -5
  105. package/types/index.d.ts +0 -2
  106. package/user/repositories/user-repository-documentdb.js +2 -27
  107. package/user/repositories/user-repository-interface.js +0 -18
  108. package/user/repositories/user-repository-mongo.js +0 -24
  109. package/user/repositories/user-repository-postgres.js +0 -18
  110. package/user/tests/doubles/test-user-repository.js +1 -1
  111. package/user/use-cases/authenticate-user.js +1 -7
  112. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  113. package/user/use-cases/get-user-from-bearer-token.js +7 -2
  114. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  115. package/user/user.js +1 -64
  116. package/credential/use-cases/credential-operations-for-user.js +0 -202
  117. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  118. package/database/encryption/encryption-config.js +0 -219
  119. package/database/encryption/test-support/fake-ciphertext.js +0 -15
  120. package/database/organization-where.js +0 -18
  121. package/database/unique-violation.js +0 -25
  122. package/extensions/bootstrap-runner.js +0 -43
  123. package/extensions/extension-loader.js +0 -187
  124. package/extensions/index.js +0 -41
  125. package/extensions/initialize-app.js +0 -123
  126. package/extensions/route-mounter.js +0 -103
  127. package/extensions/schema-composer.js +0 -188
  128. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -220
  129. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  130. package/integrations/repositories/integration-not-found.js +0 -14
  131. package/integrations/schedule-config.js +0 -80
  132. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  133. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  134. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  135. package/management-api/create-management-api-router.js +0 -49
  136. package/management-api/error-response.js +0 -116
  137. package/management-api/management-api-config.js +0 -151
  138. package/management-api/meta-router.js +0 -76
  139. package/management-api/openapi/build-openapi.js +0 -144
  140. package/management-api/openapi/components.js +0 -408
  141. package/management-api/openapi/v1-operations.js +0 -83
  142. package/management-api/openapi/v2-operations.js +0 -264
  143. package/management-api/route-registry.js +0 -411
  144. package/management-api/test-support/http-client.js +0 -39
  145. package/management-api/use-cases/get-management-api-meta.js +0 -57
  146. package/management-api/v1-deprecation.js +0 -120
  147. package/management-api/v2/create-v2-router.js +0 -85
  148. package/management-api/v2/dependencies.js +0 -281
  149. package/management-api/v2/dto.js +0 -143
  150. package/management-api/v2/handlers/authorization.js +0 -94
  151. package/management-api/v2/handlers/credentials.js +0 -33
  152. package/management-api/v2/handlers/entities.js +0 -77
  153. package/management-api/v2/handlers/integrations.js +0 -128
  154. package/management-api/v2/handlers/proxy.js +0 -34
  155. package/management-api/v2/request.js +0 -85
  156. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  157. package/modules/domain/authorization-session.js +0 -144
  158. package/modules/entity-scope.js +0 -15
  159. package/modules/entity-type-registry.js +0 -311
  160. package/modules/proxy/proxy-errors.js +0 -20
  161. package/modules/proxy/proxy-path.js +0 -179
  162. package/modules/proxy/proxy-policy.js +0 -248
  163. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  164. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  165. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  166. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  167. package/modules/repositories/entity-not-found.js +0 -18
  168. package/modules/requester/proxy-request.js +0 -244
  169. package/modules/scope-config.js +0 -192
  170. package/modules/scope-policy.js +0 -112
  171. package/modules/scope-resolution.js +0 -288
  172. package/modules/scope-visibility.js +0 -247
  173. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  174. package/modules/tests/doubles/ownership-matching.js +0 -18
  175. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  176. package/modules/use-cases/create-entity-from-credential.js +0 -113
  177. package/modules/use-cases/entity-operations-for-user.js +0 -162
  178. package/modules/use-cases/entity-types.js +0 -139
  179. package/modules/use-cases/execute-entity-proxy-request.js +0 -286
  180. package/modules/use-cases/get-authorization-requirements.js +0 -100
  181. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  182. package/modules/use-cases/manage-access.js +0 -21
  183. package/modules/use-cases/multi-step-authorization.js +0 -329
  184. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  185. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  186. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  187. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  188. package/providers/index.js +0 -23
  189. package/providers/resolve-provider.js +0 -93
  190. package/providers/runtime-detection.js +0 -124
  191. package/queues/providers/index.js +0 -11
  192. package/queues/providers/netlify-background-provider.js +0 -187
  193. package/queues/providers/qstash-queue-provider.js +0 -219
  194. package/queues/providers/sqs-queue-provider.js +0 -70
  195. package/queues/queue-client-interface.js +0 -55
  196. package/queues/queue-message-auth.js +0 -193
  197. package/queues/queue-provider-factory.js +0 -101
  198. package/queues/queue-provider.js +0 -65
  199. package/queues/sqs-queue-client.js +0 -81
  200. package/types/modules/index.d.ts +0 -197
  201. package/types/user/index.d.ts +0 -134
  202. package/user/adopter-jwt-config.js +0 -177
  203. package/user/adopter-jwt-errors.js +0 -59
  204. package/user/adopter-token-verifier.js +0 -122
  205. package/user/app-user-helpers.js +0 -97
  206. package/user/create-user-authentication.js +0 -64
  207. package/user/membership.js +0 -51
  208. package/user/repositories/organization-membership-record.js +0 -19
  209. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  210. package/user/repositories/organization-membership-repository-factory.js +0 -42
  211. package/user/repositories/organization-membership-repository-interface.js +0 -57
  212. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  213. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  214. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  215. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  216. package/user/use-cases/ensure-organization-membership.js +0 -92
  217. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -6,12 +6,6 @@ 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');
15
9
 
16
10
  const log = getLogger('frigg.http');
17
11
 
@@ -32,15 +26,11 @@ const createApp = (applyMiddleware) => {
32
26
  if (applyMiddleware) applyMiddleware(app);
33
27
 
34
28
  // 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.
38
29
  app.use((err, req, res, next) => {
39
- const boomError = toBoom(err);
30
+ const boomError = err.isBoom ? err : Boom.boomify(err);
40
31
  const {
41
32
  output: { statusCode = 500 },
42
33
  } = boomError;
43
- const versioned = isVersionedManagementApiPath(req.path);
44
34
 
45
35
  if (statusCode >= 500) {
46
36
  log.error('Request failed', {
@@ -49,6 +39,7 @@ const createApp = (applyMiddleware) => {
49
39
  invocation: summarizeExpressRequest(req),
50
40
  error: boomError,
51
41
  });
42
+ res.status(statusCode).json({ error: 'Internal Server Error' });
52
43
  } else {
53
44
  // A client error needs no stack; the logger scrubs the reason.
54
45
  log.warn('Request rejected', {
@@ -56,21 +47,8 @@ const createApp = (applyMiddleware) => {
56
47
  statusCode,
57
48
  reason: boomError.message,
58
49
  });
50
+ res.status(statusCode).json({ error: err.message });
59
51
  }
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 });
74
52
  });
75
53
 
76
54
  return app;
@@ -18,9 +18,8 @@ const {
18
18
  createModuleRepository,
19
19
  } = require('../modules/repositories/module-repository-factory');
20
20
  const {
21
- getEntityTypeRegistry,
22
- getEntityTypeDefinitions,
23
- } = require('../modules/entity-type-registry');
21
+ getModulesDefinitionFromIntegrationClasses,
22
+ } = require('../integrations/utils/map-integration-dto');
24
23
  const { processNotFound } = require('../integrations/use-cases/process-errors');
25
24
 
26
25
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
@@ -62,18 +61,30 @@ const initializeRepositories = () => {
62
61
  return { processRepository, integrationRepository, moduleRepository };
63
62
  };
64
63
 
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
+
65
77
  const loadIntegrationForWebhook = async (integrationId) => {
66
78
  const { loadAppDefinition } = require('./app-definition-loader');
67
- const { integrations: integrationClasses, moduleDefinitions } =
68
- loadAppDefinition();
79
+ const { integrations: integrationClasses } = loadAppDefinition();
69
80
 
70
81
  const { integrationRepository, moduleRepository } =
71
82
  initializeRepositories();
72
83
 
73
- const moduleFactory = new ModuleFactory({
84
+ const moduleFactory = createModuleFactoryWithDefinitions(
74
85
  moduleRepository,
75
- moduleDefinitions,
76
- });
86
+ integrationClasses
87
+ );
77
88
 
78
89
  const getIntegrationInstance = new GetIntegrationInstance({
79
90
  integrationRepository,
@@ -118,23 +129,13 @@ const integrationExists = async (integrationId) => {
118
129
  }
119
130
  };
120
131
 
121
- const loadIntegrationForProcess = async (
122
- processId,
123
- integrationClass,
124
- moduleDefinitions
125
- ) => {
132
+ const loadIntegrationForProcess = async (processId, integrationClass) => {
126
133
  const { processRepository, integrationRepository, moduleRepository } =
127
134
  initializeRepositories();
128
135
 
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
- });
136
+ const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
137
+ integrationClass,
138
+ ]);
138
139
 
139
140
  const getIntegrationInstance = new GetIntegrationInstance({
140
141
  integrationRepository,
@@ -160,13 +161,7 @@ const loadIntegrationForProcess = async (
160
161
  return instance;
161
162
  };
162
163
 
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 } = {}) => {
164
+ const createQueueWorker = (integrationClass) => {
170
165
  const integrationName = integrationClass.Definition.name;
171
166
 
172
167
  class QueueWorker extends Worker {
@@ -188,8 +183,7 @@ const createQueueWorker = (integrationClass, { moduleDefinitions } = {}) => {
188
183
  console.log(`[QueueWorker] hydrating by processId`, logCtx);
189
184
  integrationInstance = await loadIntegrationForProcess(
190
185
  params.data.processId,
191
- integrationClass,
192
- moduleDefinitions
186
+ integrationClass
193
187
  );
194
188
  console.log(`[QueueWorker] hydrated`, {
195
189
  ...logCtx,
@@ -29,19 +29,6 @@ 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
-
45
32
  /**
46
33
  * Validate admin API key from request header
47
34
  * @param {import('express').Request} req
@@ -83,4 +70,4 @@ function validateAdminApiKey(req, res, next) {
83
70
  next();
84
71
  }
85
72
 
86
- module.exports = { validateAdminApiKey, isValidAdminApiKey };
73
+ module.exports = { validateAdminApiKey };
@@ -1,51 +1,15 @@
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');
1
+ const { createIntegrationRouter } = require('@friggframework/core');
8
2
  const { createAppHandler } = require('./../app-handler-helpers');
9
3
 
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;
4
+ const router = createIntegrationRouter();
19
5
 
20
- function buildRouter() {
21
- const built = express.Router();
22
- built.use(createManagementApiRouter());
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
+ });
23
12
 
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
- });
13
+ const handler = createAppHandler('HTTP Event: Auth', router);
32
14
 
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
- };
15
+ module.exports = { handler };
@@ -2,6 +2,9 @@ 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');
5
8
  const {
6
9
  createModuleRepository,
7
10
  } = require('../../modules/repositories/module-repository-factory');
@@ -25,25 +28,18 @@ const {
25
28
  CheckIntegrationsHealthUseCase,
26
29
  } = require('../use-cases/check-integrations-health-use-case');
27
30
 
28
- const {
29
- createManagementApiMetaRouter,
30
- createDocsRedirectRouter,
31
- } = require('../../management-api/meta-router');
32
-
33
31
  const router = Router();
34
32
  const healthCheckRepository = createHealthCheckRepository({ prismaClient: prisma });
35
33
 
36
34
  // Load integrations and create factories just like auth router does
37
35
  // This verifies the system can properly load integrations
38
36
  let moduleFactory, integrationClasses;
39
- let appDefinition = {};
40
37
  try {
41
38
  const appDef = loadAppDefinition();
42
- appDefinition = appDef;
43
39
  integrationClasses = appDef.integrations || [];
44
40
 
45
41
  const moduleRepository = createModuleRepository();
46
- const { moduleDefinitions } = appDef;
42
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrationClasses);
47
43
 
48
44
  moduleFactory = new ModuleFactory({
49
45
  moduleRepository,
@@ -89,11 +85,6 @@ const validateApiKey = (req, res, next) => {
89
85
  next();
90
86
  };
91
87
 
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
-
97
88
  router.use(validateApiKey);
98
89
 
99
90
  // Helper to detect VPC configuration
@@ -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 };