@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.
- package/CHANGELOG.md +0 -16
- package/CLAUDE.md +41 -29
- package/README.md +0 -105
- package/application/commands/README.md +17 -11
- package/application/commands/entity-commands.js +18 -60
- package/application/commands/integration-commands.js +73 -20
- package/application/commands/scheduler-commands.js +7 -11
- package/application/commands/user-commands.js +5 -17
- package/application/index.js +2 -6
- package/core/Worker.js +7 -31
- package/core/create-handler.js +0 -9
- package/credential/repositories/credential-repository-documentdb.js +0 -34
- package/credential/repositories/credential-repository-interface.js +0 -15
- package/credential/repositories/credential-repository-mongo.js +0 -23
- package/credential/repositories/credential-repository-postgres.js +0 -28
- package/database/config.js +24 -48
- package/database/documentdb-encryption-service.js +22 -9
- package/database/documentdb-utils.js +0 -2
- package/database/encryption/README.md +17 -81
- package/database/encryption/documentdb-encryption-service.md +1 -78
- package/database/encryption/encryption-schema-registry.js +46 -123
- package/database/encryption/field-encryption-service.js +5 -69
- package/database/encryption/integration-mapping-encryption.js +1 -1
- package/database/prisma.js +38 -15
- package/database/use-cases/check-encryption-health-use-case.js +52 -43
- package/errors/index.js +4 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/edge.js +4 -43
- package/generated/prisma-mongodb/index-browser.js +1 -40
- package/generated/prisma-mongodb/index.d.ts +111 -4739
- package/generated/prisma-mongodb/index.js +4 -43
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -92
- package/generated/prisma-mongodb/wasm.js +4 -43
- package/generated/prisma-postgresql/edge.js +4 -43
- package/generated/prisma-postgresql/index-browser.js +1 -40
- package/generated/prisma-postgresql/index.d.ts +33 -4966
- package/generated/prisma-postgresql/index.js +4 -43
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -87
- package/generated/prisma-postgresql/wasm.js +4 -43
- package/handlers/WEBHOOKS.md +29 -0
- package/handlers/app-definition-loader.js +19 -121
- package/handlers/app-handler-helpers.js +3 -25
- package/handlers/backend-utils.js +25 -31
- package/handlers/middleware/admin-auth.js +1 -14
- package/handlers/routers/auth.js +10 -46
- package/handlers/routers/health.js +4 -13
- package/handlers/routers/integration-defined-routers.js +92 -144
- package/handlers/routers/integration-webhook-routers.js +64 -102
- package/handlers/workers/integration-defined-workers.js +2 -5
- package/index.js +3 -65
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
- package/infrastructure/scheduler/index.js +6 -19
- package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
- package/integrations/index.js +0 -1
- package/integrations/integration-base.js +29 -0
- package/integrations/integration-router.js +96 -131
- package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
- package/integrations/repositories/integration-mapping-repository.js +29 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -35
- package/integrations/repositories/integration-repository-interface.js +31 -17
- package/integrations/repositories/integration-repository-mongo.js +129 -60
- package/integrations/repositories/integration-repository-postgres.js +134 -62
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +75 -72
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -7
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +14 -43
- package/jest-setup.js +13 -2
- package/jest-teardown.js +5 -2
- package/jest.config.js +4 -0
- package/jest.db.config.js +23 -0
- package/modules/index.js +0 -14
- package/modules/module-factory.js +0 -12
- package/modules/module.js +2 -53
- package/modules/repositories/module-repository-documentdb.js +6 -51
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +74 -67
- package/modules/repositories/module-repository-postgres.js +74 -66
- package/modules/repositories/module-repository.js +1 -2
- package/modules/requester/requester.js +2 -27
- package/modules/use-cases/get-entities-for-user.js +13 -26
- package/modules/use-cases/get-entity-options-by-id.js +4 -10
- package/modules/use-cases/get-entity-options-by-type.js +1 -1
- package/modules/use-cases/get-module-instance-from-type.js +1 -1
- package/modules/use-cases/get-module.js +4 -10
- package/modules/use-cases/process-authorization-callback.js +19 -53
- package/modules/use-cases/refresh-entity-options.js +4 -10
- package/modules/use-cases/test-module-auth.js +4 -10
- package/modules/utils/map-module-dto.js +5 -3
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +7 -6
- package/prisma-mongodb/schema.prisma +0 -92
- package/prisma-postgresql/schema.prisma +0 -87
- package/queues/index.js +0 -25
- package/queues/queuer-util.js +6 -5
- package/types/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +2 -27
- package/user/repositories/user-repository-interface.js +0 -18
- package/user/repositories/user-repository-mongo.js +0 -24
- package/user/repositories/user-repository-postgres.js +0 -18
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/authenticate-user.js +1 -7
- package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
- package/user/use-cases/get-user-from-bearer-token.js +7 -2
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +1 -64
- package/credential/use-cases/credential-operations-for-user.js +0 -202
- package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
- package/database/encryption/encryption-config.js +0 -219
- package/database/encryption/test-support/fake-ciphertext.js +0 -15
- package/database/organization-where.js +0 -18
- package/database/unique-violation.js +0 -25
- package/extensions/bootstrap-runner.js +0 -43
- package/extensions/extension-loader.js +0 -187
- package/extensions/index.js +0 -41
- package/extensions/initialize-app.js +0 -123
- package/extensions/route-mounter.js +0 -103
- package/extensions/schema-composer.js +0 -188
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -220
- package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/schedule-config.js +0 -80
- package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
- package/integrations/use-cases/integration-operations-for-user.js +0 -256
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/management-api/create-management-api-router.js +0 -49
- package/management-api/error-response.js +0 -116
- package/management-api/management-api-config.js +0 -151
- package/management-api/meta-router.js +0 -76
- package/management-api/openapi/build-openapi.js +0 -144
- package/management-api/openapi/components.js +0 -408
- package/management-api/openapi/v1-operations.js +0 -83
- package/management-api/openapi/v2-operations.js +0 -264
- package/management-api/route-registry.js +0 -411
- package/management-api/test-support/http-client.js +0 -39
- package/management-api/use-cases/get-management-api-meta.js +0 -57
- package/management-api/v1-deprecation.js +0 -120
- package/management-api/v2/create-v2-router.js +0 -85
- package/management-api/v2/dependencies.js +0 -281
- package/management-api/v2/dto.js +0 -143
- package/management-api/v2/handlers/authorization.js +0 -94
- package/management-api/v2/handlers/credentials.js +0 -33
- package/management-api/v2/handlers/entities.js +0 -77
- package/management-api/v2/handlers/integrations.js +0 -128
- package/management-api/v2/handlers/proxy.js +0 -34
- package/management-api/v2/request.js +0 -85
- package/management-api/v2/test-support/fake-dependencies.js +0 -103
- package/modules/domain/authorization-session.js +0 -144
- package/modules/entity-scope.js +0 -15
- package/modules/entity-type-registry.js +0 -311
- package/modules/proxy/proxy-errors.js +0 -20
- package/modules/proxy/proxy-path.js +0 -179
- package/modules/proxy/proxy-policy.js +0 -248
- package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
- package/modules/repositories/authorization-session-repository-factory.js +0 -31
- package/modules/repositories/authorization-session-repository-interface.js +0 -47
- package/modules/repositories/authorization-session-repository-prisma.js +0 -87
- package/modules/repositories/entity-not-found.js +0 -18
- package/modules/requester/proxy-request.js +0 -244
- package/modules/scope-config.js +0 -192
- package/modules/scope-policy.js +0 -112
- package/modules/scope-resolution.js +0 -288
- package/modules/scope-visibility.js +0 -247
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -61
- package/modules/use-cases/create-entity-from-credential.js +0 -113
- package/modules/use-cases/entity-operations-for-user.js +0 -162
- package/modules/use-cases/entity-types.js +0 -139
- package/modules/use-cases/execute-entity-proxy-request.js +0 -286
- package/modules/use-cases/get-authorization-requirements.js +0 -100
- package/modules/use-cases/get-entity-module-for-user.js +0 -113
- package/modules/use-cases/manage-access.js +0 -21
- package/modules/use-cases/multi-step-authorization.js +0 -329
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
- package/providers/index.js +0 -23
- package/providers/resolve-provider.js +0 -93
- package/providers/runtime-detection.js +0 -124
- package/queues/providers/index.js +0 -11
- package/queues/providers/netlify-background-provider.js +0 -187
- package/queues/providers/qstash-queue-provider.js +0 -219
- package/queues/providers/sqs-queue-provider.js +0 -70
- package/queues/queue-client-interface.js +0 -55
- package/queues/queue-message-auth.js +0 -193
- package/queues/queue-provider-factory.js +0 -101
- package/queues/queue-provider.js +0 -65
- package/queues/sqs-queue-client.js +0 -81
- package/types/modules/index.d.ts +0 -197
- package/types/user/index.d.ts +0 -134
- package/user/adopter-jwt-config.js +0 -177
- package/user/adopter-jwt-errors.js +0 -59
- package/user/adopter-token-verifier.js +0 -122
- package/user/app-user-helpers.js +0 -97
- package/user/create-user-authentication.js +0 -64
- package/user/membership.js +0 -51
- package/user/repositories/organization-membership-record.js +0 -19
- package/user/repositories/organization-membership-repository-documentdb.js +0 -106
- package/user/repositories/organization-membership-repository-factory.js +0 -42
- package/user/repositories/organization-membership-repository-interface.js +0 -57
- package/user/repositories/organization-membership-repository-mongo.js +0 -13
- package/user/repositories/organization-membership-repository-postgres.js +0 -29
- package/user/repositories/organization-membership-repository-prisma.js +0 -81
- package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
- package/user/use-cases/ensure-organization-membership.js +0 -92
- 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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
// Skip if webhooks not enabled
|
|
19
|
+
if (
|
|
20
|
+
!webhookConfig ||
|
|
21
|
+
(typeof webhookConfig === 'object' && !webhookConfig.enabled)
|
|
22
|
+
) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
for (const entry of buildWebhookRouters(loadAppDefinition().integrations)) {
|
|
116
|
-
handlers[entry.key] = {
|
|
78
|
+
handlers[`${IntegrationClass.Definition.name}Webhook`] = {
|
|
117
79
|
handler: createAppHandler(
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
package/integrations/index.js
CHANGED
|
@@ -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
|
*/
|