@friggframework/core 2.0.0--canary.686.3e27a92.0 → 2.0.0--canary.663.8de669c.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 (184) 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 +1 -6
  15. package/database/encryption/encryption-config.js +31 -10
  16. package/database/encryption/encryption-schema-registry.js +12 -93
  17. package/database/prisma.js +2 -4
  18. package/generated/prisma-mongodb/edge.js +4 -43
  19. package/generated/prisma-mongodb/index-browser.js +1 -40
  20. package/generated/prisma-mongodb/index.d.ts +111 -4739
  21. package/generated/prisma-mongodb/index.js +4 -43
  22. package/generated/prisma-mongodb/package.json +1 -1
  23. package/generated/prisma-mongodb/schema.prisma +0 -92
  24. package/generated/prisma-mongodb/wasm.js +4 -43
  25. package/generated/prisma-postgresql/edge.js +4 -43
  26. package/generated/prisma-postgresql/index-browser.js +1 -40
  27. package/generated/prisma-postgresql/index.d.ts +33 -4966
  28. package/generated/prisma-postgresql/index.js +4 -43
  29. package/generated/prisma-postgresql/package.json +1 -1
  30. package/generated/prisma-postgresql/schema.prisma +0 -87
  31. package/generated/prisma-postgresql/wasm.js +4 -43
  32. package/handlers/app-definition-loader.js +19 -121
  33. package/handlers/app-handler-helpers.js +3 -25
  34. package/handlers/backend-utils.js +25 -31
  35. package/handlers/middleware/admin-auth.js +1 -14
  36. package/handlers/routers/auth.js +10 -46
  37. package/handlers/routers/health.js +4 -13
  38. package/handlers/routers/integration-defined-routers.js +92 -144
  39. package/handlers/routers/integration-webhook-routers.js +64 -102
  40. package/handlers/workers/integration-defined-workers.js +2 -5
  41. package/index.js +1 -65
  42. package/infrastructure/scheduler/index.js +6 -19
  43. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  44. package/integrations/index.js +0 -1
  45. package/integrations/integration-router.js +96 -131
  46. package/integrations/repositories/integration-repository-documentdb.js +3 -35
  47. package/integrations/repositories/integration-repository-interface.js +1 -17
  48. package/integrations/repositories/integration-repository-mongo.js +76 -59
  49. package/integrations/repositories/integration-repository-postgres.js +76 -62
  50. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  51. package/integrations/use-cases/create-integration.js +20 -55
  52. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  53. package/integrations/use-cases/index.js +1 -7
  54. package/integrations/utils/map-integration-dto.js +14 -43
  55. package/modules/index.js +0 -14
  56. package/modules/module-factory.js +0 -12
  57. package/modules/module.js +2 -53
  58. package/modules/repositories/module-repository-documentdb.js +6 -51
  59. package/modules/repositories/module-repository-interface.js +2 -19
  60. package/modules/repositories/module-repository-mongo.js +74 -67
  61. package/modules/repositories/module-repository-postgres.js +74 -66
  62. package/modules/repositories/module-repository.js +1 -2
  63. package/modules/requester/requester.js +2 -27
  64. package/modules/use-cases/get-entities-for-user.js +13 -26
  65. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  66. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  67. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  68. package/modules/use-cases/get-module.js +4 -10
  69. package/modules/use-cases/process-authorization-callback.js +19 -53
  70. package/modules/use-cases/refresh-entity-options.js +4 -10
  71. package/modules/use-cases/test-module-auth.js +4 -10
  72. package/package.json +5 -6
  73. package/prisma-mongodb/schema.prisma +0 -92
  74. package/prisma-postgresql/schema.prisma +0 -87
  75. package/queues/index.js +0 -25
  76. package/types/index.d.ts +0 -2
  77. package/user/repositories/user-repository-documentdb.js +2 -27
  78. package/user/repositories/user-repository-interface.js +0 -18
  79. package/user/repositories/user-repository-mongo.js +0 -24
  80. package/user/repositories/user-repository-postgres.js +0 -18
  81. package/user/use-cases/authenticate-user.js +1 -7
  82. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  83. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  84. package/user/user.js +1 -64
  85. package/credential/use-cases/credential-operations-for-user.js +0 -202
  86. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  87. package/database/organization-where.js +0 -18
  88. package/database/unique-violation.js +0 -25
  89. package/extensions/bootstrap-runner.js +0 -43
  90. package/extensions/extension-loader.js +0 -187
  91. package/extensions/index.js +0 -41
  92. package/extensions/initialize-app.js +0 -123
  93. package/extensions/route-mounter.js +0 -103
  94. package/extensions/schema-composer.js +0 -188
  95. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  96. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  97. package/integrations/repositories/integration-not-found.js +0 -14
  98. package/integrations/schedule-config.js +0 -80
  99. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  100. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  101. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  102. package/management-api/create-management-api-router.js +0 -49
  103. package/management-api/error-response.js +0 -116
  104. package/management-api/management-api-config.js +0 -151
  105. package/management-api/meta-router.js +0 -76
  106. package/management-api/openapi/build-openapi.js +0 -144
  107. package/management-api/openapi/components.js +0 -408
  108. package/management-api/openapi/v1-operations.js +0 -83
  109. package/management-api/openapi/v2-operations.js +0 -264
  110. package/management-api/route-registry.js +0 -411
  111. package/management-api/test-support/http-client.js +0 -39
  112. package/management-api/use-cases/get-management-api-meta.js +0 -57
  113. package/management-api/v1-deprecation.js +0 -120
  114. package/management-api/v2/create-v2-router.js +0 -85
  115. package/management-api/v2/dependencies.js +0 -281
  116. package/management-api/v2/dto.js +0 -143
  117. package/management-api/v2/handlers/authorization.js +0 -94
  118. package/management-api/v2/handlers/credentials.js +0 -33
  119. package/management-api/v2/handlers/entities.js +0 -77
  120. package/management-api/v2/handlers/integrations.js +0 -128
  121. package/management-api/v2/handlers/proxy.js +0 -34
  122. package/management-api/v2/request.js +0 -85
  123. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  124. package/modules/domain/authorization-session.js +0 -144
  125. package/modules/entity-scope.js +0 -15
  126. package/modules/entity-type-registry.js +0 -311
  127. package/modules/proxy/proxy-errors.js +0 -20
  128. package/modules/proxy/proxy-path.js +0 -179
  129. package/modules/proxy/proxy-policy.js +0 -248
  130. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  131. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  132. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  133. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  134. package/modules/repositories/entity-not-found.js +0 -18
  135. package/modules/requester/proxy-request.js +0 -244
  136. package/modules/scope-config.js +0 -192
  137. package/modules/scope-policy.js +0 -112
  138. package/modules/scope-resolution.js +0 -288
  139. package/modules/scope-visibility.js +0 -247
  140. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  141. package/modules/tests/doubles/ownership-matching.js +0 -18
  142. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  143. package/modules/use-cases/create-entity-from-credential.js +0 -113
  144. package/modules/use-cases/entity-operations-for-user.js +0 -162
  145. package/modules/use-cases/entity-types.js +0 -139
  146. package/modules/use-cases/execute-entity-proxy-request.js +0 -286
  147. package/modules/use-cases/get-authorization-requirements.js +0 -100
  148. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  149. package/modules/use-cases/manage-access.js +0 -21
  150. package/modules/use-cases/multi-step-authorization.js +0 -329
  151. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  152. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  153. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  154. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  155. package/providers/index.js +0 -23
  156. package/providers/resolve-provider.js +0 -93
  157. package/providers/runtime-detection.js +0 -124
  158. package/queues/providers/index.js +0 -11
  159. package/queues/providers/netlify-background-provider.js +0 -150
  160. package/queues/providers/qstash-queue-provider.js +0 -219
  161. package/queues/providers/sqs-queue-provider.js +0 -70
  162. package/queues/queue-client-interface.js +0 -55
  163. package/queues/queue-message-auth.js +0 -193
  164. package/queues/queue-provider-factory.js +0 -101
  165. package/queues/queue-provider.js +0 -65
  166. package/queues/sqs-queue-client.js +0 -81
  167. package/types/modules/index.d.ts +0 -197
  168. package/types/user/index.d.ts +0 -134
  169. package/user/adopter-jwt-config.js +0 -177
  170. package/user/adopter-jwt-errors.js +0 -59
  171. package/user/adopter-token-verifier.js +0 -122
  172. package/user/app-user-helpers.js +0 -97
  173. package/user/create-user-authentication.js +0 -64
  174. package/user/membership.js +0 -51
  175. package/user/repositories/organization-membership-record.js +0 -19
  176. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  177. package/user/repositories/organization-membership-repository-factory.js +0 -42
  178. package/user/repositories/organization-membership-repository-interface.js +0 -57
  179. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  180. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  181. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  182. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  183. package/user/use-cases/ensure-organization-membership.js +0 -92
  184. 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 };