@friggframework/core 2.0.0--canary.680.daae24e.0 → 2.0.0--canary.686.e94de4d.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 -11
- package/CLAUDE.md +28 -0
- package/README.md +105 -0
- package/application/commands/integration-commands.js +2 -2
- package/application/commands/scheduler-commands.js +11 -7
- package/application/commands/user-commands.js +17 -5
- package/core/Worker.js +31 -7
- package/core/create-handler.js +9 -0
- package/credential/use-cases/credential-operations-for-user.js +110 -25
- package/credential/use-cases/reauthorize-credential-for-user.js +15 -8
- package/database/config.js +48 -24
- package/database/documentdb-utils.js +6 -1
- package/database/encryption/README.md +0 -2
- package/database/encryption/encryption-config.js +10 -31
- package/database/encryption/encryption-schema-registry.js +99 -47
- package/database/encryption/field-encryption-service.js +5 -69
- package/database/organization-where.js +18 -0
- package/database/prisma.js +4 -2
- package/database/unique-violation.js +25 -0
- package/extensions/bootstrap-runner.js +43 -0
- package/extensions/extension-loader.js +187 -0
- package/extensions/index.js +41 -0
- package/extensions/initialize-app.js +123 -0
- package/extensions/route-mounter.js +103 -0
- package/extensions/schema-composer.js +188 -0
- package/generated/prisma-mongodb/edge.js +27 -4
- package/generated/prisma-mongodb/index-browser.js +24 -1
- package/generated/prisma-mongodb/index.d.ts +3227 -47
- package/generated/prisma-mongodb/index.js +27 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +65 -0
- package/generated/prisma-mongodb/wasm.js +27 -4
- package/generated/prisma-postgresql/edge.js +27 -4
- package/generated/prisma-postgresql/index-browser.js +24 -1
- package/generated/prisma-postgresql/index.d.ts +3519 -67
- package/generated/prisma-postgresql/index.js +27 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +61 -0
- package/generated/prisma-postgresql/wasm.js +27 -4
- package/handlers/app-definition-loader.js +103 -12
- package/handlers/routers/auth.js +40 -12
- package/handlers/routers/health.js +4 -2
- package/handlers/routers/integration-defined-routers.js +144 -92
- package/handlers/routers/integration-webhook-routers.js +102 -64
- package/index.js +63 -1
- package/infrastructure/scheduler/index.js +19 -6
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
- package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
- package/integrations/index.js +5 -0
- package/integrations/integration-router.js +115 -73
- package/integrations/repositories/integration-not-found.js +14 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -3
- package/integrations/repositories/integration-repository-interface.js +17 -1
- package/integrations/repositories/integration-repository-mongo.js +59 -76
- package/integrations/repositories/integration-repository-postgres.js +62 -76
- package/integrations/schedule-config.js +80 -0
- package/integrations/tests/doubles/test-integration-repository.js +12 -1
- package/integrations/use-cases/create-integration.js +55 -20
- package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
- package/integrations/use-cases/get-integrations-for-user.js +80 -40
- package/integrations/use-cases/index.js +7 -1
- package/integrations/use-cases/integration-operations-for-user.js +55 -24
- package/integrations/use-cases/resolve-integration-ownership.js +177 -0
- package/integrations/utils/map-integration-dto.js +44 -1
- package/management-api/management-api-config.js +71 -2
- package/management-api/meta-router.js +21 -2
- package/management-api/openapi/components.js +40 -0
- package/management-api/openapi/v2-operations.js +16 -4
- package/management-api/route-registry.js +123 -6
- package/management-api/v2/dependencies.js +40 -15
- package/management-api/v2/dto.js +20 -0
- package/management-api/v2/handlers/authorization.js +1 -0
- package/management-api/v2/handlers/credentials.js +6 -2
- package/management-api/v2/handlers/entities.js +10 -7
- package/management-api/v2/handlers/integrations.js +6 -3
- package/management-api/v2/request.js +32 -1
- package/modules/entity-scope.js +15 -0
- package/modules/index.js +8 -0
- package/modules/module-factory.js +12 -0
- package/modules/module.js +53 -2
- package/modules/repositories/entity-not-found.js +18 -0
- package/modules/repositories/module-repository-documentdb.js +51 -6
- package/modules/repositories/module-repository-interface.js +19 -2
- package/modules/repositories/module-repository-mongo.js +67 -74
- package/modules/repositories/module-repository-postgres.js +66 -74
- package/modules/repositories/module-repository.js +2 -1
- package/modules/scope-config.js +192 -0
- package/modules/scope-policy.js +112 -0
- package/modules/scope-resolution.js +288 -0
- package/modules/scope-visibility.js +247 -0
- package/modules/tests/doubles/ownership-matching.js +18 -0
- package/modules/use-cases/authorize-entity-for-user.js +61 -0
- package/modules/use-cases/create-entity-from-credential.js +113 -0
- package/modules/use-cases/entity-operations-for-user.js +50 -13
- package/modules/use-cases/entity-types.js +31 -7
- package/modules/use-cases/execute-entity-proxy-request.js +50 -3
- package/modules/use-cases/get-authorization-requirements.js +2 -2
- package/modules/use-cases/get-entities-for-user.js +26 -13
- package/modules/use-cases/get-entity-module-for-user.js +34 -7
- package/modules/use-cases/get-entity-options-by-id.js +10 -4
- 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 +10 -4
- package/modules/use-cases/manage-access.js +21 -0
- package/modules/use-cases/multi-step-authorization.js +40 -13
- package/modules/use-cases/process-authorization-callback.js +53 -19
- package/modules/use-cases/refresh-entity-options.js +10 -4
- package/modules/use-cases/test-module-auth.js +10 -4
- package/package.json +6 -5
- package/prisma-mongodb/schema.prisma +65 -0
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
- package/prisma-postgresql/schema.prisma +61 -0
- package/providers/index.js +23 -0
- package/providers/resolve-provider.js +93 -0
- package/providers/runtime-detection.js +124 -0
- package/queues/index.js +25 -0
- package/queues/providers/index.js +11 -0
- package/queues/providers/netlify-background-provider.js +150 -0
- package/queues/providers/qstash-queue-provider.js +219 -0
- package/queues/providers/sqs-queue-provider.js +70 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-message-auth.js +193 -0
- package/queues/queue-provider-factory.js +101 -0
- package/queues/queue-provider.js +65 -0
- package/queues/sqs-queue-client.js +81 -0
- package/types/index.d.ts +1 -0
- package/types/modules/index.d.ts +139 -0
- package/types/user/index.d.ts +134 -0
- package/user/adopter-jwt-config.js +177 -0
- package/user/adopter-jwt-errors.js +59 -0
- package/user/adopter-token-verifier.js +122 -0
- package/user/app-user-helpers.js +97 -0
- package/user/create-user-authentication.js +64 -0
- package/user/membership.js +51 -0
- package/user/repositories/organization-membership-record.js +19 -0
- package/user/repositories/organization-membership-repository-documentdb.js +106 -0
- package/user/repositories/organization-membership-repository-factory.js +42 -0
- package/user/repositories/organization-membership-repository-interface.js +57 -0
- package/user/repositories/organization-membership-repository-mongo.js +13 -0
- package/user/repositories/organization-membership-repository-postgres.js +29 -0
- package/user/repositories/organization-membership-repository-prisma.js +81 -0
- package/user/repositories/user-repository-documentdb.js +27 -2
- package/user/repositories/user-repository-interface.js +18 -0
- package/user/repositories/user-repository-mongo.js +24 -0
- package/user/repositories/user-repository-postgres.js +18 -0
- package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
- package/user/use-cases/authenticate-user.js +7 -1
- package/user/use-cases/ensure-organization-membership.js +92 -0
- package/user/use-cases/find-individual-users-by-organization.js +54 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
- package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
- package/user/user.js +64 -1
- package/database/encryption/test-support/fake-ciphertext.js +0 -15
|
@@ -1,117 +1,169 @@
|
|
|
1
1
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
|
-
const {
|
|
3
|
-
loadAppDefinition,
|
|
4
|
-
} = require('../app-definition-loader');
|
|
2
|
+
const { loadAppDefinition } = require('../app-definition-loader');
|
|
5
3
|
const express = require('express');
|
|
6
4
|
const { Router } = express;
|
|
7
5
|
const { loadRouterFromObject } = require('../backend-utils');
|
|
8
6
|
const { getExtensionRoutes } = require('../../integrations/extension');
|
|
9
7
|
|
|
10
|
-
const handlers = {};
|
|
11
|
-
const { integrations: integrationClasses } = loadAppDefinition();
|
|
12
|
-
|
|
13
8
|
const routeKey = (method, path) => `${(method || 'ANY').toUpperCase()} ${path}`;
|
|
14
9
|
|
|
15
10
|
// Serverless function keys must be alphanumeric; binding keys are developer-chosen.
|
|
16
11
|
const sanitizeBindingKey = (name) => String(name).replace(/[^A-Za-z0-9]/g, '');
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
for
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
);
|
|
36
49
|
|
|
37
|
-
|
|
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
|
+
}
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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})`
|
|
52
97
|
);
|
|
53
|
-
const method = (routeDef.method || 'ANY').toUpperCase();
|
|
54
|
-
const fullPath = `${basePath}${routeDef.path}`;
|
|
55
|
-
console.log(`│ ${method} ${fullPath}`);
|
|
56
98
|
}
|
|
57
|
-
|
|
99
|
+
console.log('│');
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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}`);
|
|
108
|
+
|
|
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,
|
|
73
128
|
});
|
|
129
|
+
keys.add(fnKey);
|
|
74
130
|
}
|
|
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
|
-
);
|
|
83
131
|
}
|
|
84
|
-
|
|
132
|
+
return entries;
|
|
133
|
+
}
|
|
134
|
+
|
|
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);
|
|
147
|
+
}
|
|
148
|
+
return combined;
|
|
149
|
+
}
|
|
85
150
|
|
|
86
|
-
|
|
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] = {
|
|
87
157
|
handler: createAppHandler(
|
|
88
|
-
|
|
89
|
-
router
|
|
158
|
+
entry.eventName,
|
|
159
|
+
entry.router,
|
|
160
|
+
entry.useDatabase
|
|
90
161
|
),
|
|
91
162
|
};
|
|
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
|
-
}
|
|
115
163
|
}
|
|
116
164
|
|
|
117
|
-
module.exports = {
|
|
165
|
+
module.exports = {
|
|
166
|
+
handlers,
|
|
167
|
+
buildIntegrationRouters,
|
|
168
|
+
createIntegrationDefinedRouter,
|
|
169
|
+
};
|
|
@@ -6,82 +6,120 @@ const {
|
|
|
6
6
|
IntegrationEventDispatcher,
|
|
7
7
|
} = require('../integration-event-dispatcher');
|
|
8
8
|
|
|
9
|
-
const handlers = {};
|
|
10
|
-
const { integrations: integrationClasses } = loadAppDefinition();
|
|
11
|
-
|
|
12
9
|
// `webhooks.received` is counted at the DB-connected ON_WEBHOOK dispatch (see
|
|
13
10
|
// usage-rollup-subscriber), not in this DB-free receipt handler.
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
// Skip if webhooks not enabled
|
|
26
|
+
if (
|
|
27
|
+
!webhookConfig ||
|
|
28
|
+
(typeof webhookConfig === 'object' && !webhookConfig.enabled)
|
|
29
|
+
) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
const router = Router();
|
|
34
|
+
const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
console.log(
|
|
37
|
+
`\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`
|
|
38
|
+
);
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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}`);
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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('│');
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
|
|
89
|
+
entries.push({
|
|
90
|
+
key: `${IntegrationClass.Definition.name}Webhook`,
|
|
91
|
+
eventName: `HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
|
|
81
92
|
router,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return entries;
|
|
96
|
+
}
|
|
97
|
+
|
|
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
|
+
}
|
|
113
|
+
|
|
114
|
+
const handlers = {};
|
|
115
|
+
for (const entry of buildWebhookRouters(loadAppDefinition().integrations)) {
|
|
116
|
+
handlers[entry.key] = {
|
|
117
|
+
handler: createAppHandler(
|
|
118
|
+
entry.eventName,
|
|
119
|
+
entry.router,
|
|
82
120
|
false // shouldUseDatabase = false
|
|
83
121
|
),
|
|
84
122
|
};
|
|
85
123
|
}
|
|
86
124
|
|
|
87
|
-
module.exports = { handlers };
|
|
125
|
+
module.exports = { handlers, buildWebhookRouters, createWebhookRouter };
|
package/index.js
CHANGED
|
@@ -25,6 +25,14 @@ 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');
|
|
28
36
|
const {
|
|
29
37
|
GetUserFromXFriggHeaders,
|
|
30
38
|
} = require('./user/use-cases/get-user-from-x-frigg-headers');
|
|
@@ -63,6 +71,7 @@ const {
|
|
|
63
71
|
Options,
|
|
64
72
|
createIntegrationRouter,
|
|
65
73
|
checkRequiredParams,
|
|
74
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
66
75
|
LoadIntegrationContextUseCase,
|
|
67
76
|
} = require('./integrations/index');
|
|
68
77
|
const {
|
|
@@ -100,11 +109,34 @@ const {
|
|
|
100
109
|
ModuleFactory,
|
|
101
110
|
getEntityTypeRegistry,
|
|
102
111
|
EntityRegistryError,
|
|
112
|
+
resolveScope,
|
|
113
|
+
listBindingScopes,
|
|
114
|
+
OwnershipConfigError,
|
|
103
115
|
} = require('./modules/index');
|
|
116
|
+
const {
|
|
117
|
+
ManagementApiConfigError,
|
|
118
|
+
} = require('./management-api/management-api-config');
|
|
104
119
|
const application = require('./application');
|
|
105
120
|
const utils = require('./utils');
|
|
106
121
|
|
|
107
|
-
const {
|
|
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');
|
|
108
140
|
|
|
109
141
|
module.exports = {
|
|
110
142
|
// assertions
|
|
@@ -131,6 +163,10 @@ module.exports = {
|
|
|
131
163
|
createUserRepository,
|
|
132
164
|
UserRepositoryMongo,
|
|
133
165
|
UserRepositoryPostgres,
|
|
166
|
+
createOrganizationMembershipRepository,
|
|
167
|
+
OrganizationMembershipRepositoryMongo,
|
|
168
|
+
OrganizationMembershipRepositoryPostgres,
|
|
169
|
+
EnsureOrganizationMembership,
|
|
134
170
|
GetUserFromXFriggHeaders,
|
|
135
171
|
GetUserFromAdopterJwt,
|
|
136
172
|
AuthenticateUser,
|
|
@@ -151,6 +187,8 @@ module.exports = {
|
|
|
151
187
|
Options,
|
|
152
188
|
checkRequiredParams,
|
|
153
189
|
createIntegrationRouter,
|
|
190
|
+
// Deprecated (ADR-059): use getEntityTypeRegistry.
|
|
191
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
154
192
|
LoadIntegrationContextUseCase,
|
|
155
193
|
CreateProcess,
|
|
156
194
|
UpdateProcessState,
|
|
@@ -208,8 +246,32 @@ module.exports = {
|
|
|
208
246
|
ModuleFactory,
|
|
209
247
|
getEntityTypeRegistry,
|
|
210
248
|
EntityRegistryError,
|
|
249
|
+
resolveScope,
|
|
250
|
+
listBindingScopes,
|
|
251
|
+
OwnershipConfigError,
|
|
252
|
+
ManagementApiConfigError,
|
|
211
253
|
// queues
|
|
212
254
|
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,
|
|
213
275
|
|
|
214
276
|
// utils
|
|
215
277
|
...utils,
|
|
@@ -5,13 +5,19 @@
|
|
|
5
5
|
* Follows hexagonal architecture with interface + adapters pattern.
|
|
6
6
|
*
|
|
7
7
|
* Providers:
|
|
8
|
-
* - eventbridge: AWS EventBridge Scheduler (production)
|
|
8
|
+
* - eventbridge: AWS EventBridge Scheduler (production on AWS)
|
|
9
|
+
* - netlify: poll-and-dispatch from a Netlify scheduled function
|
|
9
10
|
* - 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).
|
|
10
18
|
*/
|
|
11
19
|
|
|
12
20
|
const { SchedulerServiceInterface } = require('./scheduler-service-interface');
|
|
13
|
-
const { EventBridgeSchedulerAdapter } = require('./eventbridge-scheduler-adapter');
|
|
14
|
-
const { MockSchedulerAdapter } = require('./mock-scheduler-adapter');
|
|
15
21
|
const {
|
|
16
22
|
createSchedulerService,
|
|
17
23
|
SCHEDULER_PROVIDERS,
|
|
@@ -22,9 +28,16 @@ module.exports = {
|
|
|
22
28
|
// Interface (Port)
|
|
23
29
|
SchedulerServiceInterface,
|
|
24
30
|
|
|
25
|
-
// Adapters
|
|
26
|
-
EventBridgeSchedulerAdapter
|
|
27
|
-
|
|
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
|
+
},
|
|
28
41
|
|
|
29
42
|
// Factory
|
|
30
43
|
createSchedulerService,
|