@shipfox/api-integration-core-dto 12.0.0 → 14.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 +1 -1
- package/CHANGELOG.md +17 -0
- package/dist/events.d.ts +27 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +14 -0
- package/dist/events.js.map +1 -1
- package/dist/inter-module.d.ts +88 -37
- package/dist/inter-module.d.ts.map +1 -1
- package/dist/inter-module.js +33 -0
- package/dist/inter-module.js.map +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/integrations.d.ts +25 -19
- package/dist/schemas/integrations.d.ts.map +1 -1
- package/dist/schemas/integrations.js +8 -0
- package/dist/schemas/integrations.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/webhooks.d.ts +8 -8
- package/package.json +1 -1
- package/src/events.test.ts +70 -0
- package/src/events.ts +19 -0
- package/src/inter-module.test.ts +76 -0
- package/src/inter-module.ts +21 -0
- package/src/schemas/index.ts +1 -0
- package/src/schemas/integrations.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/webhooks.d.ts
CHANGED
|
@@ -6,14 +6,14 @@ export declare const WEBHOOK_MAX_HEADER_BYTES: number;
|
|
|
6
6
|
export declare const WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH: number;
|
|
7
7
|
export declare const webhookRouteIds: readonly ["github", "gitea", "linear", "sentry", "slack.event", "slack.command", "webhook.connection", "jira"];
|
|
8
8
|
export declare const webhookRouteIdSchema: z.ZodEnum<{
|
|
9
|
-
github: "github";
|
|
10
9
|
gitea: "gitea";
|
|
10
|
+
github: "github";
|
|
11
|
+
jira: "jira";
|
|
11
12
|
linear: "linear";
|
|
12
13
|
sentry: "sentry";
|
|
13
|
-
"slack.event": "slack.event";
|
|
14
14
|
"slack.command": "slack.command";
|
|
15
|
+
"slack.event": "slack.event";
|
|
15
16
|
"webhook.connection": "webhook.connection";
|
|
16
|
-
jira: "jira";
|
|
17
17
|
}>;
|
|
18
18
|
export type WebhookRouteId = z.infer<typeof webhookRouteIdSchema>;
|
|
19
19
|
declare const webhookBodySchema: z.ZodObject<{
|
|
@@ -32,12 +32,12 @@ export declare const storedWebhookRequestSchema: z.ZodDiscriminatedUnion<[z.ZodO
|
|
|
32
32
|
data: z.ZodString;
|
|
33
33
|
}, z.core.$strict>;
|
|
34
34
|
route_id: z.ZodEnum<{
|
|
35
|
-
github: "github";
|
|
36
35
|
gitea: "gitea";
|
|
36
|
+
github: "github";
|
|
37
37
|
linear: "linear";
|
|
38
38
|
sentry: "sentry";
|
|
39
|
-
"slack.event": "slack.event";
|
|
40
39
|
"slack.command": "slack.command";
|
|
40
|
+
"slack.event": "slack.event";
|
|
41
41
|
}>;
|
|
42
42
|
path_parameters: z.ZodObject<{}, z.core.$strict>;
|
|
43
43
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -82,12 +82,12 @@ export declare const webhookProcessingResultSchema: z.ZodDiscriminatedUnion<[z.Z
|
|
|
82
82
|
}, z.core.$strict>, z.ZodObject<{
|
|
83
83
|
outcome: z.ZodLiteral<"discarded">;
|
|
84
84
|
reason: z.ZodEnum<{
|
|
85
|
-
|
|
85
|
+
connection_unavailable: "connection_unavailable";
|
|
86
86
|
invalid_signature: "invalid_signature";
|
|
87
|
-
stale_at_receipt: "stale_at_receipt";
|
|
88
87
|
malformed_payload: "malformed_payload";
|
|
88
|
+
missing_required_input: "missing_required_input";
|
|
89
|
+
stale_at_receipt: "stale_at_receipt";
|
|
89
90
|
unsupported_event: "unsupported_event";
|
|
90
|
-
connection_unavailable: "connection_unavailable";
|
|
91
91
|
}>;
|
|
92
92
|
deliveryId: z.ZodOptional<z.ZodString>;
|
|
93
93
|
}, z.core.$strict>], "outcome">;
|
package/package.json
CHANGED
package/src/events.test.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
+
INTEGRATION_CONNECTION_AVAILABLE,
|
|
2
3
|
INTEGRATION_EVENT_RECEIVED,
|
|
3
4
|
INTEGRATION_SOURCE_COMMIT_PUSHED,
|
|
4
5
|
INTEGRATION_SOURCE_REPOSITORY_UPDATED,
|
|
6
|
+
integrationConnectionAvailableSchema,
|
|
5
7
|
integrationEventReceivedSchema,
|
|
6
8
|
integrationSourceCommitPushedSchema,
|
|
7
9
|
integrationSourceRepositoryUpdatedSchema,
|
|
8
10
|
integrationsEventSchemas,
|
|
9
11
|
} from './events.js';
|
|
10
12
|
|
|
13
|
+
const validConnectionAvailable = {
|
|
14
|
+
provider: 'linear',
|
|
15
|
+
workspaceId: 'ws-1',
|
|
16
|
+
connectionId: 'conn-1',
|
|
17
|
+
slug: 'linear_shipfox',
|
|
18
|
+
capabilities: ['agent_tools'],
|
|
19
|
+
};
|
|
20
|
+
|
|
11
21
|
const validEventReceived = {
|
|
12
22
|
provider: 'github',
|
|
13
23
|
source: 'github',
|
|
@@ -49,6 +59,65 @@ const validRepositoryUpdated = {
|
|
|
49
59
|
},
|
|
50
60
|
};
|
|
51
61
|
|
|
62
|
+
describe('integrationConnectionAvailableSchema', () => {
|
|
63
|
+
it('parses a valid connection-available payload unchanged', () => {
|
|
64
|
+
expect(integrationConnectionAvailableSchema.parse(validConnectionAvailable)).toEqual(
|
|
65
|
+
validConnectionAvailable,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('carries both capabilities for a source-control and tool provider', () => {
|
|
70
|
+
const input = {...validConnectionAvailable, capabilities: ['source_control', 'agent_tools']};
|
|
71
|
+
|
|
72
|
+
expect(integrationConnectionAvailableSchema.parse(input)).toEqual(input);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('carries an empty capabilities array for a connection without adapters', () => {
|
|
76
|
+
const input = {...validConnectionAvailable, capabilities: []};
|
|
77
|
+
|
|
78
|
+
expect(integrationConnectionAvailableSchema.parse(input)).toEqual(input);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('defaults capabilities for an event written before the field existed', () => {
|
|
82
|
+
const {capabilities: _capabilities, ...withoutCapabilities} = validConnectionAvailable;
|
|
83
|
+
|
|
84
|
+
expect(integrationConnectionAvailableSchema.parse(withoutCapabilities)).toEqual({
|
|
85
|
+
...withoutCapabilities,
|
|
86
|
+
capabilities: [],
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('rejects unknown or empty capability values', () => {
|
|
91
|
+
expect(() =>
|
|
92
|
+
integrationConnectionAvailableSchema.parse({
|
|
93
|
+
...validConnectionAvailable,
|
|
94
|
+
capabilities: [''],
|
|
95
|
+
}),
|
|
96
|
+
).toThrow();
|
|
97
|
+
expect(() =>
|
|
98
|
+
integrationConnectionAvailableSchema.parse({
|
|
99
|
+
...validConnectionAvailable,
|
|
100
|
+
capabilities: ['agent-tools'],
|
|
101
|
+
}),
|
|
102
|
+
).toThrow();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('rejects capability arrays above the contract bound', () => {
|
|
106
|
+
expect(() =>
|
|
107
|
+
integrationConnectionAvailableSchema.parse({
|
|
108
|
+
...validConnectionAvailable,
|
|
109
|
+
capabilities: Array.from({length: 17}, () => 'agent_tools'),
|
|
110
|
+
}),
|
|
111
|
+
).toThrow();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('rejects a payload without a connection slug', () => {
|
|
115
|
+
const {slug: _slug, ...withoutSlug} = validConnectionAvailable;
|
|
116
|
+
|
|
117
|
+
expect(() => integrationConnectionAvailableSchema.parse(withoutSlug)).toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
52
121
|
describe('integrationSourceCommitPushedSchema', () => {
|
|
53
122
|
it('parses a valid commit-pushed payload unchanged', () => {
|
|
54
123
|
const result = integrationSourceCommitPushedSchema.parse(validCommitPushed);
|
|
@@ -136,6 +205,7 @@ describe('integrationsEventSchemas', () => {
|
|
|
136
205
|
|
|
137
206
|
expect(registeredTypes).toEqual(
|
|
138
207
|
[
|
|
208
|
+
INTEGRATION_CONNECTION_AVAILABLE,
|
|
139
209
|
INTEGRATION_EVENT_RECEIVED,
|
|
140
210
|
INTEGRATION_SOURCE_COMMIT_PUSHED,
|
|
141
211
|
INTEGRATION_SOURCE_REPOSITORY_UPDATED,
|
package/src/events.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {z} from 'zod';
|
|
2
|
+
import {integrationCapabilitySchema} from './schemas/integrations.js';
|
|
2
3
|
|
|
3
4
|
export const INTEGRATION_EVENT_RECEIVED = 'integrations.event.received' as const;
|
|
5
|
+
export const INTEGRATION_CONNECTION_AVAILABLE = 'integrations.connection.available' as const;
|
|
4
6
|
|
|
5
7
|
const nonEmptyStringSchema = z.string().nonempty();
|
|
6
8
|
const isoDateTimeSchema = z.string().datetime();
|
|
@@ -22,6 +24,21 @@ export const integrationEventReceivedSchema = z.object({
|
|
|
22
24
|
});
|
|
23
25
|
export type IntegrationEventReceivedEvent = z.infer<typeof integrationEventReceivedSchema>;
|
|
24
26
|
|
|
27
|
+
export const integrationConnectionAvailableSchema = z.object({
|
|
28
|
+
provider: nonEmptyStringSchema,
|
|
29
|
+
workspaceId: nonEmptyStringSchema,
|
|
30
|
+
connectionId: nonEmptyStringSchema,
|
|
31
|
+
slug: nonEmptyStringSchema,
|
|
32
|
+
// Provider registry capabilities, so subscribers can tell a tool connection
|
|
33
|
+
// from a source-control connection without their own provider table.
|
|
34
|
+
// Defaulting keeps availability events written before this field existed
|
|
35
|
+
// deliverable while every new event is still constrained to known capabilities.
|
|
36
|
+
capabilities: z.array(integrationCapabilitySchema).max(16).default([]),
|
|
37
|
+
});
|
|
38
|
+
export type IntegrationConnectionAvailableEvent = z.infer<
|
|
39
|
+
typeof integrationConnectionAvailableSchema
|
|
40
|
+
>;
|
|
41
|
+
|
|
25
42
|
// A source-control push, normalized by the producing provider and carried by
|
|
26
43
|
// `INTEGRATION_SOURCE_COMMIT_PUSHED` for domain consumers.
|
|
27
44
|
export const sourcePushSchema = z.object({
|
|
@@ -107,12 +124,14 @@ export const SENTRY_ISSUE_ACTIONS = [
|
|
|
107
124
|
export type SentryIssueAction = (typeof SENTRY_ISSUE_ACTIONS)[number];
|
|
108
125
|
|
|
109
126
|
export interface IntegrationsEventMap {
|
|
127
|
+
[INTEGRATION_CONNECTION_AVAILABLE]: IntegrationConnectionAvailableEvent;
|
|
110
128
|
[INTEGRATION_EVENT_RECEIVED]: IntegrationEventReceivedEvent;
|
|
111
129
|
[INTEGRATION_SOURCE_COMMIT_PUSHED]: IntegrationSourceCommitPushedEvent;
|
|
112
130
|
[INTEGRATION_SOURCE_REPOSITORY_UPDATED]: IntegrationSourceRepositoryUpdatedEvent;
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
export const integrationsEventSchemas = {
|
|
134
|
+
[INTEGRATION_CONNECTION_AVAILABLE]: integrationConnectionAvailableSchema,
|
|
116
135
|
[INTEGRATION_EVENT_RECEIVED]: integrationEventReceivedSchema,
|
|
117
136
|
[INTEGRATION_SOURCE_COMMIT_PUSHED]: integrationSourceCommitPushedSchema,
|
|
118
137
|
[INTEGRATION_SOURCE_REPOSITORY_UPDATED]: integrationSourceRepositoryUpdatedSchema,
|
package/src/inter-module.test.ts
CHANGED
|
@@ -65,6 +65,37 @@ describe('integrationsInterModuleContract', () => {
|
|
|
65
65
|
expect(integrationsInterModuleContract.methods.resolveConnection.output.parse(null)).toBeNull();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
test('accepts a resolved source ref through the producer contract', () => {
|
|
69
|
+
const result = integrationsInterModuleContract.methods.resolveSourceRef.output.parse({
|
|
70
|
+
ref: 'refs/heads/fix-triage-prompt',
|
|
71
|
+
commit: 'a'.repeat(40),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual({ref: 'refs/heads/fix-triage-prompt', commit: 'a'.repeat(40)});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('parses a ref-bearing input through the producer contract', () => {
|
|
78
|
+
const input = integrationsInterModuleContract.methods.resolveSourceRef.input.parse({
|
|
79
|
+
workspaceId: '00000000-0000-4000-8000-000000000001',
|
|
80
|
+
connectionId: '00000000-0000-4000-8000-000000000002',
|
|
81
|
+
externalRepositoryId: 'github:42',
|
|
82
|
+
ref: 'refs/heads/main',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(input.ref).toBe('refs/heads/main');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('rejects control characters in a ref-bearing input', () => {
|
|
89
|
+
expect(() =>
|
|
90
|
+
integrationsInterModuleContract.methods.resolveSourceRef.input.parse({
|
|
91
|
+
workspaceId: '00000000-0000-4000-8000-000000000001',
|
|
92
|
+
connectionId: '00000000-0000-4000-8000-000000000002',
|
|
93
|
+
externalRepositoryId: 'github:42',
|
|
94
|
+
ref: 'refs/heads/unsafe\nname',
|
|
95
|
+
}),
|
|
96
|
+
).toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
68
99
|
test.each([
|
|
69
100
|
['connection-not-found', {connectionId: '00000000-0000-4000-8000-000000000001'}],
|
|
70
101
|
['provider-unavailable', {provider: 'github'}],
|
|
@@ -77,4 +108,49 @@ describe('integrationsInterModuleContract', () => {
|
|
|
77
108
|
|
|
78
109
|
expect(schema.parse(details)).toEqual(details);
|
|
79
110
|
});
|
|
111
|
+
|
|
112
|
+
test.each([
|
|
113
|
+
['ref-not-found', {ref: 'refs/heads/missing'}],
|
|
114
|
+
['ref-invalid', {ref: 'a'.repeat(40)}],
|
|
115
|
+
] as const)('defines the %s ref failure', (code, details) => {
|
|
116
|
+
const schema =
|
|
117
|
+
integrationsInterModuleContract.methods.resolveSourceRef.errors[
|
|
118
|
+
code as keyof typeof integrationsInterModuleContract.methods.resolveSourceRef.errors
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
expect(schema.parse(details)).toEqual(details);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('accepts provider event catalogs and fixed event providers on the validation context', () => {
|
|
125
|
+
const output = integrationsInterModuleContract.methods.getAgentToolsContext.output.parse({
|
|
126
|
+
selectionCatalogs: [],
|
|
127
|
+
catalogs: [],
|
|
128
|
+
workspaceConnections: [],
|
|
129
|
+
eventCatalogs: [
|
|
130
|
+
{provider: 'github', events: ['push']},
|
|
131
|
+
{provider: 'webhook', events: ['received']},
|
|
132
|
+
],
|
|
133
|
+
fixedEventProviders: ['webhook'],
|
|
134
|
+
defaultConnection: null,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(output.eventCatalogs).toEqual([
|
|
138
|
+
{provider: 'github', events: ['push']},
|
|
139
|
+
{provider: 'webhook', events: ['received']},
|
|
140
|
+
]);
|
|
141
|
+
expect(output.fixedEventProviders).toEqual(['webhook']);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('rejects empty fixed event provider identifiers', () => {
|
|
145
|
+
expect(() =>
|
|
146
|
+
integrationsInterModuleContract.methods.getAgentToolsContext.output.parse({
|
|
147
|
+
selectionCatalogs: [],
|
|
148
|
+
catalogs: [],
|
|
149
|
+
workspaceConnections: [],
|
|
150
|
+
eventCatalogs: [],
|
|
151
|
+
fixedEventProviders: [''],
|
|
152
|
+
defaultConnection: null,
|
|
153
|
+
}),
|
|
154
|
+
).toThrow();
|
|
155
|
+
});
|
|
80
156
|
});
|
package/src/inter-module.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {z} from 'zod';
|
|
|
4
4
|
const id = z.string().uuid();
|
|
5
5
|
const provider = z.string().min(1);
|
|
6
6
|
const capability = z.enum(['source_control', 'agent_tools']);
|
|
7
|
+
const safeRef = z.string().refine(isSafeRefInput, 'Ref contains a control character');
|
|
7
8
|
const connection = z.object({id, provider, slug: z.string().min(1)});
|
|
8
9
|
const repository = z.object({
|
|
9
10
|
externalRepositoryId: z.string(),
|
|
@@ -36,6 +37,12 @@ const sourceErrors = {
|
|
|
36
37
|
'provider-failure': providerError,
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
const refErrors = {
|
|
41
|
+
...sourceErrors,
|
|
42
|
+
'ref-not-found': z.object({ref: safeRef}),
|
|
43
|
+
'ref-invalid': z.object({ref: safeRef}),
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
/** Producer-owned synchronous operations for the Integrations bounded context. */
|
|
40
47
|
export const integrationsInterModuleContract = defineInterModuleContract({
|
|
41
48
|
module: 'integrations',
|
|
@@ -54,6 +61,11 @@ export const integrationsInterModuleContract = defineInterModuleContract({
|
|
|
54
61
|
output: triggerReference.nullable(),
|
|
55
62
|
errors: sourceErrors,
|
|
56
63
|
},
|
|
64
|
+
resolveSourceRef: {
|
|
65
|
+
input: sourceInput.extend({ref: safeRef}),
|
|
66
|
+
output: z.object({ref: z.string(), commit: z.string()}),
|
|
67
|
+
errors: refErrors,
|
|
68
|
+
},
|
|
57
69
|
listSourceFiles: {
|
|
58
70
|
input: sourceInput.extend({
|
|
59
71
|
ref: z.string(),
|
|
@@ -135,6 +147,8 @@ export const integrationsInterModuleContract = defineInterModuleContract({
|
|
|
135
147
|
workspaceConnections: z.array(
|
|
136
148
|
z.object({slug: z.string(), id, provider, capabilities: z.array(capability)}),
|
|
137
149
|
),
|
|
150
|
+
eventCatalogs: z.array(z.object({provider, events: z.array(z.string())})),
|
|
151
|
+
fixedEventProviders: z.array(provider),
|
|
138
152
|
defaultConnection: z.object({id, slug: z.string(), provider}).nullable(),
|
|
139
153
|
}),
|
|
140
154
|
errors: sourceErrors,
|
|
@@ -142,4 +156,11 @@ export const integrationsInterModuleContract = defineInterModuleContract({
|
|
|
142
156
|
},
|
|
143
157
|
});
|
|
144
158
|
|
|
159
|
+
function isSafeRefInput(value: string): boolean {
|
|
160
|
+
return [...value].every((character) => {
|
|
161
|
+
const code = character.codePointAt(0) ?? 0;
|
|
162
|
+
return !(code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
145
166
|
export type IntegrationsModuleClient = InterModuleClient<typeof integrationsInterModuleContract>;
|
package/src/schemas/index.ts
CHANGED
|
@@ -2,6 +2,13 @@ import {z} from 'zod';
|
|
|
2
2
|
|
|
3
3
|
export const CONNECTION_SLUG_MAX_LENGTH = 100;
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Slugs sync classifies by literal: a source equal to one of these names is a
|
|
7
|
+
* built-in trigger source, never a connection. No provider may allocate a
|
|
8
|
+
* connection holding one of them.
|
|
9
|
+
*/
|
|
10
|
+
export const RESERVED_CONNECTION_SLUGS = ['manual', 'cron'] as const;
|
|
11
|
+
|
|
5
12
|
export const connectionSlugSchema = z
|
|
6
13
|
.string()
|
|
7
14
|
.min(1)
|