@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,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,
@@ -11,19 +11,65 @@
11
11
  * This adapter implements SchedulerServiceInterface for AWS EventBridge Scheduler.
12
12
  */
13
13
 
14
- const {
15
- SchedulerClient,
16
- CreateScheduleCommand,
17
- DeleteScheduleCommand,
18
- GetScheduleCommand,
19
- ResourceNotFoundException,
20
- } = require('@aws-sdk/client-scheduler');
21
-
22
14
  const { SchedulerServiceInterface } = require('./scheduler-service-interface');
23
15
 
16
+ const SCHEDULER_SDK = '@aws-sdk/client-scheduler';
17
+
18
+ /**
19
+ * Lazily load the AWS EventBridge Scheduler SDK.
20
+ *
21
+ * Requiring `@aws-sdk/client-scheduler` at module top level would put the AWS
22
+ * SDK on the load path of every consumer of `@friggframework/core`, including
23
+ * those using the mock scheduler or no scheduler at all. Loading it here, on
24
+ * construction of the EventBridge adapter, keeps it out of the module-load
25
+ * path. The adapter calls it exactly once, in its constructor, and keeps the
26
+ * classes its methods need on the instance.
27
+ *
28
+ * Only a MODULE_NOT_FOUND for the scheduler SDK itself is rewritten into an
29
+ * actionable message; any other failure (including a missing transitive
30
+ * dependency) is rethrown unchanged so the real cause is not masked.
31
+ *
32
+ * @returns {object} The relevant exports from `@aws-sdk/client-scheduler`.
33
+ */
34
+ function loadSchedulerSdk() {
35
+ try {
36
+ // Keep the literal specifier so bundlers (esbuild) can resolve it.
37
+ return require('@aws-sdk/client-scheduler');
38
+ } catch (error) {
39
+ const isSchedulerSdkMissing =
40
+ error &&
41
+ error.code === 'MODULE_NOT_FOUND' &&
42
+ typeof error.message === 'string' &&
43
+ error.message.includes(SCHEDULER_SDK);
44
+ if (isSchedulerSdkMissing) {
45
+ throw new Error(
46
+ `The EventBridge scheduler requires the "${SCHEDULER_SDK}" package, ` +
47
+ `which is not installed. Install it (\`npm install ${SCHEDULER_SDK}\`) ` +
48
+ 'to use the EventBridge scheduler provider, or use the mock scheduler provider ' +
49
+ '(SCHEDULER_PROVIDER=mock) for local development.',
50
+ { cause: error }
51
+ );
52
+ }
53
+ throw error;
54
+ }
55
+ }
56
+
24
57
  class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
25
58
  constructor({ region } = {}) {
26
59
  super();
60
+ const {
61
+ SchedulerClient,
62
+ CreateScheduleCommand,
63
+ DeleteScheduleCommand,
64
+ GetScheduleCommand,
65
+ ResourceNotFoundException,
66
+ } = loadSchedulerSdk();
67
+ this.sdk = {
68
+ CreateScheduleCommand,
69
+ DeleteScheduleCommand,
70
+ GetScheduleCommand,
71
+ ResourceNotFoundException,
72
+ };
27
73
  this.client = new SchedulerClient({
28
74
  region: region || process.env.AWS_REGION || 'us-east-1',
29
75
  });
@@ -42,7 +88,12 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
42
88
  * @param {Object} params.payload - Message payload
43
89
  * @returns {Promise<{scheduledJobId: string, scheduledAt: string}>}
44
90
  */
45
- async scheduleOneTime({ scheduleName, scheduleAt, queueResourceId, payload }) {
91
+ async scheduleOneTime({
92
+ scheduleName,
93
+ scheduleAt,
94
+ queueResourceId,
95
+ payload,
96
+ }) {
46
97
  if (!scheduleName) {
47
98
  throw new Error('scheduleName is required');
48
99
  }
@@ -59,8 +110,11 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
59
110
  }
60
111
 
61
112
  // Format date to AWS schedule expression (at(yyyy-mm-ddThh:mm:ss))
62
- const scheduleExpression = `at(${scheduleAt.toISOString().replace(/\.\d{3}Z$/, '')})`;
113
+ const scheduleExpression = `at(${scheduleAt
114
+ .toISOString()
115
+ .replace(/\.\d{3}Z$/, '')})`;
63
116
 
117
+ const { CreateScheduleCommand } = this.sdk;
64
118
  const command = new CreateScheduleCommand({
65
119
  Name: scheduleName,
66
120
  GroupName: this.scheduleGroupName,
@@ -107,6 +161,7 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
107
161
  throw new Error('scheduleName is required');
108
162
  }
109
163
 
164
+ const { DeleteScheduleCommand, ResourceNotFoundException } = this.sdk;
110
165
  const command = new DeleteScheduleCommand({
111
166
  Name: scheduleName,
112
167
  GroupName: this.scheduleGroupName,
@@ -141,6 +196,7 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
141
196
  throw new Error('scheduleName is required');
142
197
  }
143
198
 
199
+ const { GetScheduleCommand, ResourceNotFoundException } = this.sdk;
144
200
  const command = new GetScheduleCommand({
145
201
  Name: scheduleName,
146
202
  GroupName: this.scheduleGroupName,
@@ -153,9 +209,7 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
153
209
  // Format: at(yyyy-mm-ddThh:mm:ss)
154
210
  let scheduledAt = null;
155
211
  if (response.ScheduleExpression) {
156
- const match = response.ScheduleExpression.match(
157
- /^at\((.+)\)$/
158
- );
212
+ const match = response.ScheduleExpression.match(/^at\((.+)\)$/);
159
213
  if (match) {
160
214
  scheduledAt = new Date(match[1] + 'Z').toISOString();
161
215
  }
@@ -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,
@@ -7,22 +7,16 @@
7
7
  * Environment Detection:
8
8
  * - SCHEDULER_PROVIDER=eventbridge -> Use AWS EventBridge Scheduler
9
9
  * - SCHEDULER_PROVIDER=mock -> Use in-memory mock scheduler
10
- * - SCHEDULER_PROVIDER=netlify -> Netlify poll-and-dispatch scheduler
11
- * (friggframework/frigg#545; needs `repository`, usually `queueProvider`)
12
- * - Default on Netlify (NETLIFY=true, set by the Netlify runtime and build;
13
- * provider-netlify's generated functions also set SCHEDULER_PROVIDER) -> Netlify
14
10
  * - Default in dev/test/local stages -> Mock scheduler
15
11
  * - Default in other stages -> EventBridge scheduler
16
- * - Any other name -> require('@friggframework/provider-<name>').SchedulerAdapter
17
- *
18
- * Adapters are required inside their branch so building one never loads
19
- * another's SDK (@aws-sdk/client-scheduler is not a core dependency).
20
12
  */
21
13
 
14
+ const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
15
+ const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
16
+
22
17
  const SCHEDULER_PROVIDERS = {
23
18
  EVENTBRIDGE: 'eventbridge',
24
19
  MOCK: 'mock',
25
- NETLIFY: 'netlify',
26
20
  };
27
21
 
28
22
  const LOCAL_STAGES = ['dev', 'test', 'local'];
@@ -38,12 +32,6 @@ function determineProvider() {
38
32
  return explicitProvider;
39
33
  }
40
34
 
41
- // Checked before the stage: a Netlify site with no STAGE (or STAGE=dev)
42
- // must still persist schedules where scheduled-sync reads them.
43
- if (process.env.NETLIFY === 'true') {
44
- return SCHEDULER_PROVIDERS.NETLIFY;
45
- }
46
-
47
35
  const stage = process.env.STAGE || 'dev';
48
36
  if (LOCAL_STAGES.includes(stage)) {
49
37
  return SCHEDULER_PROVIDERS.MOCK;
@@ -52,13 +40,6 @@ function determineProvider() {
52
40
  return SCHEDULER_PROVIDERS.EVENTBRIDGE;
53
41
  }
54
42
 
55
- function createDefaultScheduledJobRepository() {
56
- const { ScheduledJobRepository } = require('./scheduled-job-repository');
57
- // The prisma export is a lazy proxy: nothing connects until a query runs.
58
- const { prisma } = require('../../database/prisma');
59
- return new ScheduledJobRepository({ prismaClient: prisma });
60
- }
61
-
62
43
  /**
63
44
  * Create a scheduler service instance
64
45
  *
@@ -66,51 +47,22 @@ function createDefaultScheduledJobRepository() {
66
47
  * @param {string} options.provider - Scheduler provider ('eventbridge' or 'mock')
67
48
  * @param {string} options.region - AWS region (for EventBridge)
68
49
  * @param {boolean} options.verbose - Verbose logging (for Mock)
69
- * @param {Object} [options.repository] - Schedule repository (for Netlify: persists schedules)
70
- * @param {Object} [options.queueProvider] - Queue provider (for Netlify: dispatches due jobs)
71
50
  * @returns {SchedulerServiceInterface} Implementation of scheduler interface
72
51
  */
73
52
  function createSchedulerService(options = {}) {
74
53
  const provider = options.provider || determineProvider();
75
54
 
76
55
  switch (provider) {
77
- case SCHEDULER_PROVIDERS.EVENTBRIDGE: {
78
- const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
56
+ case SCHEDULER_PROVIDERS.EVENTBRIDGE:
79
57
  return new EventBridgeSchedulerAdapter({
80
58
  region: options.region,
81
59
  });
82
- }
83
- case SCHEDULER_PROVIDERS.MOCK: {
84
- const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
60
+ case SCHEDULER_PROVIDERS.MOCK:
85
61
  return new MockSchedulerAdapter({
86
62
  verbose: options.verbose,
87
63
  });
88
- }
89
- case SCHEDULER_PROVIDERS.NETLIFY: {
90
- const { NetlifySchedulerAdapter } = require('./netlify-scheduler-adapter');
91
- return new NetlifySchedulerAdapter({
92
- // Default: the Prisma ScheduledJob rows that the Netlify
93
- // scheduled-sync function polls. Without it the adapter
94
- // falls back to memory, and a schedule made in one function
95
- // is never seen by the cron.
96
- repository: options.repository || createDefaultScheduledJobRepository(),
97
- queueProvider: options.queueProvider,
98
- });
99
- }
100
- default: {
101
- let plugin;
102
- try {
103
- plugin = require('../../providers/resolve-provider').resolveProvider(null, {
104
- provider,
105
- });
106
- } catch (error) {
107
- throw new Error(`Unknown scheduler provider: ${provider} (${error.message})`);
108
- }
109
- if (!plugin.SchedulerAdapter) {
110
- throw new Error(`Unknown scheduler provider: ${provider}`);
111
- }
112
- return new plugin.SchedulerAdapter(options);
113
- }
64
+ default:
65
+ throw new Error(`Unknown scheduler provider: ${provider}`);
114
66
  }
115
67
  }
116
68
 
@@ -21,7 +21,6 @@ module.exports = {
21
21
  Options,
22
22
  createIntegrationRouter,
23
23
  checkRequiredParams,
24
- // Deprecated (ADR-059): a wrapper over getEntityTypeRegistry.
25
24
  getModulesDefinitionFromIntegrationClasses,
26
25
  LoadIntegrationContextUseCase,
27
26
  validateExtensionBinding,
@@ -457,6 +457,35 @@ class IntegrationBase {
457
457
  );
458
458
  }
459
459
 
460
+ /**
461
+ * Insert-only mapping write; usable as an atomic claim/lock row.
462
+ * @throws {MappingAlreadyExistsError} If a mapping for sourceId exists
463
+ */
464
+ async createMapping(sourceId, mapping) {
465
+ if (!sourceId) {
466
+ throw new Error(`sourceId must be set`);
467
+ }
468
+ return await this.integrationMappingRepository.createMapping(
469
+ this.id,
470
+ sourceId,
471
+ mapping
472
+ );
473
+ }
474
+
475
+ /**
476
+ * Deletes the mapping for sourceId. A missing mapping is not an error
477
+ * (resolves with deletedCount 0).
478
+ */
479
+ async deleteMapping(sourceId) {
480
+ if (!sourceId) {
481
+ throw new Error(`sourceId must be set`);
482
+ }
483
+ return await this.integrationMappingRepository.deleteMapping(
484
+ this.id,
485
+ sourceId
486
+ );
487
+ }
488
+
460
489
  /**
461
490
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
462
491
  */