@shipfox/api-integration-core 4.0.0 → 6.0.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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +100 -0
- package/dist/core/entities/provider.d.ts +7 -2
- package/dist/core/entities/provider.d.ts.map +1 -1
- package/dist/core/entities/provider.js.map +1 -1
- package/dist/core/errors.d.ts +7 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +8 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/db/webhook-deliveries.d.ts +4 -0
- package/dist/db/webhook-deliveries.d.ts.map +1 -1
- package/dist/db/webhook-deliveries.js +10 -2
- package/dist/db/webhook-deliveries.js.map +1 -1
- package/dist/index.d.ts +19 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -6
- package/dist/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +9 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +121 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/index.d.ts +1 -0
- package/dist/presentation/routes/agent-tools-gateway/index.d.ts.map +1 -1
- package/dist/presentation/routes/agent-tools-gateway/index.js +1 -0
- package/dist/presentation/routes/agent-tools-gateway/index.js.map +1 -1
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts +5 -2
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts.map +1 -1
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js +53 -17
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -3
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +3 -3
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/manage-connections.d.ts +1 -1
- package/dist/presentation/routes/manage-connections.d.ts.map +1 -1
- package/dist/presentation/routes/manage-connections.js +28 -3
- package/dist/presentation/routes/manage-connections.js.map +1 -1
- package/dist/providers/gitea.d.ts.map +1 -1
- package/dist/providers/gitea.js +10 -8
- package/dist/providers/gitea.js.map +1 -1
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +18 -13
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/jira.d.ts.map +1 -1
- package/dist/providers/jira.js +113 -3
- package/dist/providers/jira.js.map +1 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +35 -16
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/sentry.d.ts.map +1 -1
- package/dist/providers/sentry.js +13 -11
- package/dist/providers/sentry.js.map +1 -1
- package/dist/providers/slack.d.ts.map +1 -1
- package/dist/providers/slack.js +36 -16
- package/dist/providers/slack.js.map +1 -1
- package/dist/providers/types.d.ts +12 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/webhook.d.ts.map +1 -1
- package/dist/providers/webhook.js +11 -9
- package/dist/providers/webhook.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +33 -38
- package/src/core/entities/provider.ts +7 -1
- package/src/core/errors.ts +12 -2
- package/src/db/webhook-deliveries.test.ts +11 -0
- package/src/db/webhook-deliveries.ts +12 -3
- package/src/index.test.ts +112 -0
- package/src/index.ts +104 -10
- package/src/presentation/inter-module.ts +155 -0
- package/src/presentation/routes/agent-tools-gateway/index.ts +1 -0
- package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.test.ts +36 -0
- package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.ts +64 -22
- package/src/presentation/routes/index.ts +5 -6
- package/src/presentation/routes/manage-connections.test.ts +113 -0
- package/src/presentation/routes/manage-connections.ts +26 -2
- package/src/providers/gitea.ts +11 -8
- package/src/providers/github.ts +17 -11
- package/src/providers/jira.ts +155 -7
- package/src/providers/linear.test.ts +136 -0
- package/src/providers/linear.ts +33 -13
- package/src/providers/sentry.ts +15 -12
- package/src/providers/slack.test.ts +139 -0
- package/src/providers/slack.ts +39 -14
- package/src/providers/types.ts +15 -0
- package/src/providers/webhook.ts +11 -9
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-integration-core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -13,22 +13,12 @@
|
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
14
|
"imports": {
|
|
15
15
|
"#test/*": "./test/*",
|
|
16
|
-
"#*":
|
|
17
|
-
"workspace-source": "./src/*",
|
|
18
|
-
"development": "./src/*",
|
|
19
|
-
"default": "./dist/*"
|
|
20
|
-
}
|
|
16
|
+
"#*": "./dist/*"
|
|
21
17
|
},
|
|
22
18
|
"exports": {
|
|
23
19
|
".": {
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"default": "./src/index.ts"
|
|
27
|
-
},
|
|
28
|
-
"default": {
|
|
29
|
-
"types": "./dist/index.d.ts",
|
|
30
|
-
"default": "./dist/index.js"
|
|
31
|
-
}
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
32
22
|
}
|
|
33
23
|
},
|
|
34
24
|
"dependencies": {
|
|
@@ -37,26 +27,29 @@
|
|
|
37
27
|
"ajv": "^8.20.0",
|
|
38
28
|
"drizzle-orm": "^0.45.2",
|
|
39
29
|
"zod": "^4.4.3",
|
|
40
|
-
"@shipfox/api-agent-dto": "
|
|
41
|
-
"@shipfox/api-auth-context": "
|
|
42
|
-
"@shipfox/api-
|
|
43
|
-
"@shipfox/api-integration-
|
|
44
|
-
"@shipfox/api-
|
|
45
|
-
"@shipfox/api-integration-
|
|
46
|
-
"@shipfox/api-integration-
|
|
47
|
-
"@shipfox/api-integration-
|
|
48
|
-
"@shipfox/api-integration-
|
|
49
|
-
"@shipfox/api-integration-
|
|
50
|
-
"@shipfox/
|
|
51
|
-
"@shipfox/
|
|
52
|
-
"@shipfox/
|
|
53
|
-
"@shipfox/
|
|
54
|
-
"@shipfox/node-
|
|
55
|
-
"@shipfox/node-
|
|
56
|
-
"@shipfox/node-
|
|
57
|
-
"@shipfox/node-
|
|
58
|
-
"@shipfox/
|
|
59
|
-
"@shipfox/
|
|
30
|
+
"@shipfox/api-agent-dto": "6.0.0",
|
|
31
|
+
"@shipfox/api-auth-context": "6.0.0",
|
|
32
|
+
"@shipfox/api-workflows-dto": "6.0.0",
|
|
33
|
+
"@shipfox/api-integration-core-dto": "6.0.0",
|
|
34
|
+
"@shipfox/api-workspaces-dto": "6.0.0",
|
|
35
|
+
"@shipfox/api-integration-gitea": "6.0.0",
|
|
36
|
+
"@shipfox/api-integration-github": "6.0.0",
|
|
37
|
+
"@shipfox/api-integration-jira": "6.0.0",
|
|
38
|
+
"@shipfox/api-integration-linear": "6.0.0",
|
|
39
|
+
"@shipfox/api-integration-sentry": "6.0.0",
|
|
40
|
+
"@shipfox/api-integration-slack": "6.0.0",
|
|
41
|
+
"@shipfox/api-integration-webhook": "6.0.0",
|
|
42
|
+
"@shipfox/config": "1.2.2",
|
|
43
|
+
"@shipfox/inter-module": "0.2.0",
|
|
44
|
+
"@shipfox/node-drizzle": "0.3.2",
|
|
45
|
+
"@shipfox/node-fastify": "0.2.4",
|
|
46
|
+
"@shipfox/node-module": "0.4.0",
|
|
47
|
+
"@shipfox/node-opentelemetry": "0.5.2",
|
|
48
|
+
"@shipfox/node-outbox": "0.2.4",
|
|
49
|
+
"@shipfox/node-postgres": "0.4.2",
|
|
50
|
+
"@shipfox/node-temporal": "0.3.2",
|
|
51
|
+
"@shipfox/regex": "0.2.2",
|
|
52
|
+
"@shipfox/redact": "0.2.3"
|
|
60
53
|
},
|
|
61
54
|
"devDependencies": {
|
|
62
55
|
"@temporalio/activity": "1.18.1",
|
|
@@ -69,16 +62,18 @@
|
|
|
69
62
|
"fastify": "^5.3.3",
|
|
70
63
|
"fastify-type-provider-zod": "^6.0.0",
|
|
71
64
|
"fishery": "^2.4.0",
|
|
72
|
-
"@shipfox/biome": "1.8.
|
|
73
|
-
"@shipfox/
|
|
65
|
+
"@shipfox/biome": "1.8.2",
|
|
66
|
+
"@shipfox/depcruise": "1.0.2",
|
|
67
|
+
"@shipfox/swc": "1.2.6",
|
|
74
68
|
"@shipfox/ts-config": "1.3.8",
|
|
75
|
-
"@shipfox/typescript": "1.1.
|
|
76
|
-
"@shipfox/vitest": "1.2.
|
|
69
|
+
"@shipfox/typescript": "1.1.7",
|
|
70
|
+
"@shipfox/vitest": "1.2.3"
|
|
77
71
|
},
|
|
78
72
|
"scripts": {
|
|
79
73
|
"build": "shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js",
|
|
80
74
|
"check": "shipfox-biome-check",
|
|
81
75
|
"check:fix": "shipfox-biome-check --write",
|
|
76
|
+
"depcruise": "shipfox-depcruise",
|
|
82
77
|
"test": "shipfox-vitest-run",
|
|
83
78
|
"test:watch": "shipfox-vitest-watch",
|
|
84
79
|
"type": "shipfox-tsc-check",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
IntegrationProvider as CoreIntegrationProvider,
|
|
3
3
|
RegisteredIntegrationProvider as CoreRegisteredIntegrationProvider,
|
|
4
|
+
WebhookRequestProcessor,
|
|
5
|
+
WebhookRouteId,
|
|
4
6
|
} from '@shipfox/api-integration-core-dto';
|
|
5
7
|
import type {RouteExport} from '@shipfox/node-fastify';
|
|
6
8
|
|
|
@@ -17,5 +19,9 @@ export type {
|
|
|
17
19
|
OpenAgentToolsSessionInput,
|
|
18
20
|
} from '@shipfox/api-integration-core-dto';
|
|
19
21
|
|
|
20
|
-
export type IntegrationProvider = CoreIntegrationProvider<string, RouteExport
|
|
22
|
+
export type IntegrationProvider = CoreIntegrationProvider<string, RouteExport> & {
|
|
23
|
+
webhookProcessors?:
|
|
24
|
+
| Array<{routeIds: readonly WebhookRouteId[]; processor: WebhookRequestProcessor}>
|
|
25
|
+
| undefined;
|
|
26
|
+
};
|
|
21
27
|
export type RegisteredIntegrationProvider = CoreRegisteredIntegrationProvider<string, RouteExport>;
|
package/src/core/errors.ts
CHANGED
|
@@ -35,7 +35,10 @@ export class IntegrationConnectionAlreadyExistsError extends Error {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export class IntegrationCapabilityUnavailableError extends Error {
|
|
38
|
-
constructor(
|
|
38
|
+
constructor(
|
|
39
|
+
public readonly capability: IntegrationCapability,
|
|
40
|
+
public readonly provider: IntegrationProviderKind,
|
|
41
|
+
) {
|
|
39
42
|
super(`Integration provider ${provider} does not expose ${capability}`);
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -47,9 +50,16 @@ export class IntegrationCheckoutUnsupportedError extends Error {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export class IntegrationProviderUnavailableError extends Error {
|
|
50
|
-
constructor(provider: IntegrationProviderKind) {
|
|
53
|
+
constructor(public readonly provider: IntegrationProviderKind) {
|
|
51
54
|
super(`No integration provider registered for ${provider}`);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
export class WebhookProcessorNotConfiguredError extends Error {
|
|
59
|
+
constructor(public readonly routeId: string) {
|
|
60
|
+
super(`No webhook processor is configured for ${routeId}`);
|
|
61
|
+
this.name = 'WebhookProcessorNotConfiguredError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export {ConnectionSlugConflictError, IntegrationProviderError, type IntegrationProviderErrorReason};
|
|
@@ -9,6 +9,7 @@ import {db} from './db.js';
|
|
|
9
9
|
import {integrationsOutbox} from './schema/outbox.js';
|
|
10
10
|
import {integrationsWebhookDeliveries} from './schema/webhook-deliveries.js';
|
|
11
11
|
import {
|
|
12
|
+
claimWebhookDelivery,
|
|
12
13
|
publishIntegrationEventReceived,
|
|
13
14
|
publishSourceCommitPushed,
|
|
14
15
|
publishSourcePush,
|
|
@@ -165,6 +166,16 @@ describe('integration webhook delivery persistence', () => {
|
|
|
165
166
|
|
|
166
167
|
expect(await deliveriesFor('github', deliveryId)).toHaveLength(1);
|
|
167
168
|
});
|
|
169
|
+
|
|
170
|
+
it('reports whether it claimed a delivery', async () => {
|
|
171
|
+
const deliveryId = crypto.randomUUID();
|
|
172
|
+
|
|
173
|
+
const first = await claimWebhookDelivery({tx: db(), provider: 'github', deliveryId});
|
|
174
|
+
const duplicate = await claimWebhookDelivery({tx: db(), provider: 'github', deliveryId});
|
|
175
|
+
|
|
176
|
+
expect(first.claimed).toBe(true);
|
|
177
|
+
expect(duplicate.claimed).toBe(false);
|
|
178
|
+
});
|
|
168
179
|
});
|
|
169
180
|
|
|
170
181
|
describe('publishSourcePush', () => {
|
|
@@ -171,8 +171,10 @@ export interface RecordDeliveryOnlyParams {
|
|
|
171
171
|
deliveryId: string;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
export async function
|
|
175
|
-
|
|
174
|
+
export async function claimWebhookDelivery(
|
|
175
|
+
params: RecordDeliveryOnlyParams,
|
|
176
|
+
): Promise<{claimed: boolean}> {
|
|
177
|
+
const inserted = await params.tx
|
|
176
178
|
.insert(integrationsWebhookDeliveries)
|
|
177
179
|
.values({
|
|
178
180
|
provider: params.provider,
|
|
@@ -185,7 +187,13 @@ export async function recordDeliveryOnly(params: RecordDeliveryOnlyParams): Prom
|
|
|
185
187
|
integrationsWebhookDeliveries.dedupScope,
|
|
186
188
|
integrationsWebhookDeliveries.deliveryId,
|
|
187
189
|
],
|
|
188
|
-
})
|
|
190
|
+
})
|
|
191
|
+
.returning({deliveryId: integrationsWebhookDeliveries.deliveryId});
|
|
192
|
+
return {claimed: inserted.length > 0};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function recordDeliveryOnly(params: RecordDeliveryOnlyParams): Promise<void> {
|
|
196
|
+
await claimWebhookDelivery(params);
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
export interface PruneWebhookDeliveriesParams {
|
|
@@ -204,4 +212,5 @@ export async function pruneWebhookDeliveries(
|
|
|
204
212
|
export type PublishIntegrationEventReceivedFn = typeof publishIntegrationEventReceived;
|
|
205
213
|
export type PublishSourcePushFn = typeof publishSourcePush;
|
|
206
214
|
export type PublishSourceCommitPushedFn = typeof publishSourceCommitPushed;
|
|
215
|
+
export type ClaimWebhookDeliveryFn = typeof claimWebhookDelivery;
|
|
207
216
|
export type RecordDeliveryOnlyFn = typeof recordDeliveryOnly;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
StoredWebhookRequest,
|
|
3
|
+
WebhookRequestProcessor,
|
|
4
|
+
} from '@shipfox/api-integration-core-dto';
|
|
5
|
+
import {createOutboxRegistry, type ModuleService, startModuleServices} from '@shipfox/node-module';
|
|
6
|
+
import {createIntegrationsContext, WebhookProcessorNotConfiguredError} from './index.js';
|
|
7
|
+
|
|
8
|
+
const request = {
|
|
9
|
+
schema_version: 1,
|
|
10
|
+
request_id: 'a8a44e12-f4bd-4bd1-82d4-ccdba70a9f3e',
|
|
11
|
+
route_id: 'github',
|
|
12
|
+
received_at: '2026-07-20T12:00:00.000Z',
|
|
13
|
+
method: 'POST',
|
|
14
|
+
path_parameters: {},
|
|
15
|
+
raw_query_string: '',
|
|
16
|
+
headers: {},
|
|
17
|
+
body: {encoding: 'base64', data: ''},
|
|
18
|
+
} as const satisfies StoredWebhookRequest;
|
|
19
|
+
|
|
20
|
+
describe('createIntegrationsContext', () => {
|
|
21
|
+
it('binds the composed direct-route processor to an optional delivery source', async () => {
|
|
22
|
+
const directProcessor: WebhookRequestProcessor = {
|
|
23
|
+
process: vi.fn().mockResolvedValue({outcome: 'processed', deliveryId: 'delivery-1'}),
|
|
24
|
+
};
|
|
25
|
+
const stop = vi.fn().mockResolvedValue(undefined);
|
|
26
|
+
const service: ModuleService = {
|
|
27
|
+
name: 'queued-webhook-deliveries',
|
|
28
|
+
shutdownTimeoutMs: 1_000,
|
|
29
|
+
start: vi.fn().mockResolvedValue({finished: Promise.resolve(), stop}),
|
|
30
|
+
};
|
|
31
|
+
const deliverySource = {createService: vi.fn().mockReturnValue(service)};
|
|
32
|
+
|
|
33
|
+
const context = await createIntegrationsContext({
|
|
34
|
+
parts: [
|
|
35
|
+
{
|
|
36
|
+
provider: {provider: 'github', displayName: 'GitHub', adapters: {}},
|
|
37
|
+
webhookProcessors: [{routeIds: ['github'], processor: directProcessor}],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
webhookDeliverySource: deliverySource,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(context.module.services).toEqual([service]);
|
|
44
|
+
expect(deliverySource.createService).toHaveBeenCalledWith(context.webhookProcessor);
|
|
45
|
+
|
|
46
|
+
const result = await context.webhookProcessor.process(request);
|
|
47
|
+
const services = await startModuleServices({
|
|
48
|
+
services: context.module.services ?? [],
|
|
49
|
+
context: {outboxRegistry: createOutboxRegistry()},
|
|
50
|
+
});
|
|
51
|
+
await services.stop();
|
|
52
|
+
|
|
53
|
+
expect(result).toEqual({outcome: 'processed', deliveryId: 'delivery-1'});
|
|
54
|
+
expect(directProcessor.process).toHaveBeenCalledWith(request);
|
|
55
|
+
expect(stop).toHaveBeenCalledOnce();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not register a service without a delivery source', async () => {
|
|
59
|
+
const context = await createIntegrationsContext({
|
|
60
|
+
parts: [{provider: {provider: 'github', displayName: 'GitHub', adapters: {}}}],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(context.module.services).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('rejects a queued request with no registered processor', async () => {
|
|
67
|
+
const context = await createIntegrationsContext({
|
|
68
|
+
parts: [{provider: {provider: 'github', displayName: 'GitHub', adapters: {}}}],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = context.webhookProcessor.process(request);
|
|
72
|
+
|
|
73
|
+
await expect(result).rejects.toEqual(new WebhookProcessorNotConfiguredError('github'));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('fails composition when more than one processor handles a route', async () => {
|
|
77
|
+
const processor: WebhookRequestProcessor = {
|
|
78
|
+
process: vi.fn().mockResolvedValue({outcome: 'processed', deliveryId: 'delivery-1'}),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = createIntegrationsContext({
|
|
82
|
+
parts: [
|
|
83
|
+
{
|
|
84
|
+
provider: {provider: 'github', displayName: 'GitHub', adapters: {}},
|
|
85
|
+
webhookProcessors: [
|
|
86
|
+
{routeIds: ['github'], processor},
|
|
87
|
+
{routeIds: ['github'], processor},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await expect(result).rejects.toThrow(
|
|
94
|
+
'Webhook processor is registered more than once for github',
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('fails composition when a configured delivery source is invalid', async () => {
|
|
99
|
+
const sourceError = new Error('WEBHOOK_QUEUE_URL is required');
|
|
100
|
+
|
|
101
|
+
const result = createIntegrationsContext({
|
|
102
|
+
parts: [{provider: {provider: 'github', displayName: 'GitHub', adapters: {}}}],
|
|
103
|
+
webhookDeliverySource: {
|
|
104
|
+
createService: () => {
|
|
105
|
+
throw sourceError;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await expect(result).rejects.toThrow(sourceError);
|
|
111
|
+
});
|
|
112
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import {dirname, resolve} from 'node:path';
|
|
2
2
|
import {fileURLToPath} from 'node:url';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
integrationsEventSchemas,
|
|
5
|
+
type StoredWebhookRequest,
|
|
6
|
+
type WebhookProcessingResult,
|
|
7
|
+
type WebhookRequestProcessor,
|
|
8
|
+
type WebhookRouteId,
|
|
9
|
+
} from '@shipfox/api-integration-core-dto';
|
|
10
|
+
import type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';
|
|
11
|
+
import type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';
|
|
12
|
+
import type {ModuleService, ShipfoxModule} from '@shipfox/node-module';
|
|
5
13
|
import {logger} from '@shipfox/node-opentelemetry';
|
|
6
14
|
import type {IntegrationProvider} from '#core/entities/provider.js';
|
|
15
|
+
import {WebhookProcessorNotConfiguredError} from '#core/errors.js';
|
|
7
16
|
import {
|
|
8
17
|
createIntegrationProviderRegistry,
|
|
9
18
|
type IntegrationProviderRegistry,
|
|
@@ -16,15 +25,26 @@ import {getIntegrationConnectionById} from '#db/connections.js';
|
|
|
16
25
|
import {db} from '#db/db.js';
|
|
17
26
|
import {migrationsPath} from '#db/migrations.js';
|
|
18
27
|
import {integrationsOutbox} from '#db/schema/outbox.js';
|
|
19
|
-
import {
|
|
28
|
+
import {createIntegrationsInterModulePresentation} from '#presentation/inter-module.js';
|
|
29
|
+
import {createIntegrationRoutes} from '#presentation/routes/index.js';
|
|
20
30
|
import {loadEnabledProviderModules} from '#providers/modules.js';
|
|
21
|
-
import type {
|
|
31
|
+
import type {
|
|
32
|
+
IntegrationModuleParts,
|
|
33
|
+
IntegrationProviderSecrets,
|
|
34
|
+
WebhookProcessorRegistration,
|
|
35
|
+
} from '#providers/types.js';
|
|
22
36
|
import {createIntegrationsMaintenanceActivities} from '#temporal/activities/index.js';
|
|
23
37
|
import {INTEGRATIONS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';
|
|
24
38
|
|
|
25
39
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
40
|
const maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');
|
|
27
41
|
|
|
42
|
+
export type {
|
|
43
|
+
StoredWebhookRequest,
|
|
44
|
+
WebhookProcessingResult,
|
|
45
|
+
WebhookRequestProcessor,
|
|
46
|
+
WebhookRouteId,
|
|
47
|
+
} from '@shipfox/api-integration-core-dto';
|
|
28
48
|
export {
|
|
29
49
|
buildProviderRepositoryId,
|
|
30
50
|
MAX_REPOSITORY_FILE_BYTES,
|
|
@@ -63,6 +83,7 @@ export {
|
|
|
63
83
|
IntegrationConnectionWorkspaceMismatchError,
|
|
64
84
|
IntegrationProviderError,
|
|
65
85
|
IntegrationProviderUnavailableError,
|
|
86
|
+
WebhookProcessorNotConfiguredError,
|
|
66
87
|
} from '#core/errors.js';
|
|
67
88
|
export type {
|
|
68
89
|
AgentToolCallInput,
|
|
@@ -98,6 +119,7 @@ export {createSourceControlIntegrationService} from '#core/source-control-servic
|
|
|
98
119
|
export type {GetIntegrationConnectionByIdFn} from '#db/connections.js';
|
|
99
120
|
export {getIntegrationConnectionById} from '#db/connections.js';
|
|
100
121
|
export type {
|
|
122
|
+
ClaimWebhookDeliveryFn,
|
|
101
123
|
PublishIntegrationEventReceivedFn,
|
|
102
124
|
PublishIntegrationEventReceivedParams,
|
|
103
125
|
PublishIntegrationEventReceivedResult,
|
|
@@ -106,7 +128,7 @@ export type {
|
|
|
106
128
|
RecordDeliveryOnlyFn,
|
|
107
129
|
RecordDeliveryOnlyParams,
|
|
108
130
|
} from '#db/webhook-deliveries.js';
|
|
109
|
-
export {pruneWebhookDeliveries} from '#db/webhook-deliveries.js';
|
|
131
|
+
export {claimWebhookDelivery, pruneWebhookDeliveries} from '#db/webhook-deliveries.js';
|
|
110
132
|
export {integrationRouteErrorHandler} from '#presentation/routes/errors.js';
|
|
111
133
|
|
|
112
134
|
export interface CreateIntegrationsModuleOptions {
|
|
@@ -118,11 +140,21 @@ export interface CreateIntegrationsModuleOptions {
|
|
|
118
140
|
*/
|
|
119
141
|
parts?: IntegrationModuleParts[] | undefined;
|
|
120
142
|
secrets?: IntegrationProviderSecrets | undefined;
|
|
143
|
+
workspaces?: WorkspacesInterModuleClient | undefined;
|
|
121
144
|
agentTools?:
|
|
122
145
|
| {
|
|
123
|
-
|
|
146
|
+
workflows: WorkflowsModuleClient;
|
|
124
147
|
}
|
|
125
148
|
| undefined;
|
|
149
|
+
webhookDeliverySource?: WebhookDeliverySource | undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Hosted runtimes implement this port to receive stored webhook requests. The
|
|
154
|
+
* integration module starts its returned service after migrations complete.
|
|
155
|
+
*/
|
|
156
|
+
export interface WebhookDeliverySource {
|
|
157
|
+
createService(processor: WebhookRequestProcessor): ModuleService;
|
|
126
158
|
}
|
|
127
159
|
|
|
128
160
|
export interface IntegrationsContext {
|
|
@@ -132,6 +164,7 @@ export interface IntegrationsContext {
|
|
|
132
164
|
sourceControl: IntegrationSourceControlService;
|
|
133
165
|
};
|
|
134
166
|
sourceControl: IntegrationSourceControlService;
|
|
167
|
+
webhookProcessor: WebhookRequestProcessor;
|
|
135
168
|
/**
|
|
136
169
|
* Runs every enabled provider's one-shot boot-time tasks, after modules are initialized
|
|
137
170
|
* (migrations done). Failures are isolated and logged, never rethrown, so a provider task
|
|
@@ -149,17 +182,41 @@ export async function createIntegrationsModule(
|
|
|
149
182
|
export async function createIntegrationsContext(
|
|
150
183
|
options: CreateIntegrationsModuleOptions = {},
|
|
151
184
|
): Promise<IntegrationsContext> {
|
|
185
|
+
const workspaces = options.workspaces;
|
|
152
186
|
const parts: IntegrationModuleParts[] =
|
|
153
187
|
options.parts ??
|
|
154
188
|
(options.providers
|
|
155
|
-
? options.providers.map((provider) => ({
|
|
156
|
-
|
|
189
|
+
? options.providers.map((provider) => ({
|
|
190
|
+
provider,
|
|
191
|
+
webhookProcessors: provider.webhookProcessors,
|
|
192
|
+
}))
|
|
193
|
+
: await loadEnabledProviderModules({
|
|
194
|
+
secrets: options.secrets,
|
|
195
|
+
...(workspaces
|
|
196
|
+
? {
|
|
197
|
+
requireActiveWorkspaceMembership: (input: {
|
|
198
|
+
workspaceId: string;
|
|
199
|
+
userId: string;
|
|
200
|
+
memberships: ReadonlyArray<
|
|
201
|
+
import('@shipfox/api-auth-context').UserContextMembership
|
|
202
|
+
>;
|
|
203
|
+
}) =>
|
|
204
|
+
workspaces.requireActiveMembership({
|
|
205
|
+
...input,
|
|
206
|
+
memberships: [...input.memberships],
|
|
207
|
+
}),
|
|
208
|
+
}
|
|
209
|
+
: {}),
|
|
210
|
+
}));
|
|
157
211
|
|
|
158
212
|
const registry = createIntegrationProviderRegistry(parts.map((part) => part.provider));
|
|
159
213
|
const sourceControl = createSourceControlIntegrationService({
|
|
160
214
|
registry,
|
|
161
215
|
getIntegrationConnectionById,
|
|
162
216
|
});
|
|
217
|
+
const webhookProcessor = createComposedWebhookProcessor(
|
|
218
|
+
parts.flatMap((part) => part.webhookProcessors ?? []),
|
|
219
|
+
);
|
|
163
220
|
|
|
164
221
|
async function runStartupTasks(): Promise<void> {
|
|
165
222
|
for (const task of parts.flatMap((part) => part.startupTasks ?? [])) {
|
|
@@ -174,6 +231,9 @@ export async function createIntegrationsContext(
|
|
|
174
231
|
|
|
175
232
|
const module: ShipfoxModule = {
|
|
176
233
|
name: 'integrations',
|
|
234
|
+
interModulePresentations: [
|
|
235
|
+
createIntegrationsInterModulePresentation({registry, sourceControl}),
|
|
236
|
+
],
|
|
177
237
|
startupTasks: runStartupTasks,
|
|
178
238
|
database: [
|
|
179
239
|
{db, migrationsPath},
|
|
@@ -182,7 +242,7 @@ export async function createIntegrationsContext(
|
|
|
182
242
|
routes: createIntegrationRoutes(registry, sourceControl, {
|
|
183
243
|
agentTools: options.agentTools
|
|
184
244
|
? {
|
|
185
|
-
|
|
245
|
+
workflows: options.agentTools.workflows,
|
|
186
246
|
getIntegrationConnectionById,
|
|
187
247
|
}
|
|
188
248
|
: undefined,
|
|
@@ -206,7 +266,41 @@ export async function createIntegrationsContext(
|
|
|
206
266
|
},
|
|
207
267
|
...parts.flatMap((part) => part.workers ?? []),
|
|
208
268
|
],
|
|
269
|
+
...(options.webhookDeliverySource
|
|
270
|
+
? {services: [options.webhookDeliverySource.createService(webhookProcessor)]}
|
|
271
|
+
: {}),
|
|
209
272
|
};
|
|
210
273
|
|
|
211
|
-
return {
|
|
274
|
+
return {
|
|
275
|
+
module,
|
|
276
|
+
registry,
|
|
277
|
+
capabilities: {sourceControl},
|
|
278
|
+
sourceControl,
|
|
279
|
+
webhookProcessor,
|
|
280
|
+
runStartupTasks,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createComposedWebhookProcessor(
|
|
285
|
+
registrations: WebhookProcessorRegistration[],
|
|
286
|
+
): WebhookRequestProcessor {
|
|
287
|
+
const processors = new Map<WebhookRouteId, WebhookRequestProcessor>();
|
|
288
|
+
for (const registration of registrations) {
|
|
289
|
+
for (const routeId of registration.routeIds) {
|
|
290
|
+
if (processors.has(routeId)) {
|
|
291
|
+
throw new Error(`Webhook processor is registered more than once for ${routeId}`);
|
|
292
|
+
}
|
|
293
|
+
processors.set(routeId, registration.processor);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
async process(request: StoredWebhookRequest): Promise<WebhookProcessingResult> {
|
|
299
|
+
const processor = processors.get(request.route_id);
|
|
300
|
+
if (!processor) {
|
|
301
|
+
throw new WebhookProcessorNotConfiguredError(request.route_id);
|
|
302
|
+
}
|
|
303
|
+
return await processor.process(request);
|
|
304
|
+
},
|
|
305
|
+
};
|
|
212
306
|
}
|