@shipfox/api-integration-core-dto 12.2.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 +11 -0
- package/dist/events.d.ts +8 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +7 -1
- package/dist/events.js.map +1 -1
- package/dist/inter-module.d.ts +51 -0
- 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 +6 -0
- 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/package.json +1 -1
- package/src/events.test.ts +46 -0
- package/src/events.ts +6 -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/package.json
CHANGED
package/src/events.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ const validConnectionAvailable = {
|
|
|
15
15
|
workspaceId: 'ws-1',
|
|
16
16
|
connectionId: 'conn-1',
|
|
17
17
|
slug: 'linear_shipfox',
|
|
18
|
+
capabilities: ['agent_tools'],
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const validEventReceived = {
|
|
@@ -65,6 +66,51 @@ describe('integrationConnectionAvailableSchema', () => {
|
|
|
65
66
|
);
|
|
66
67
|
});
|
|
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
|
+
|
|
68
114
|
it('rejects a payload without a connection slug', () => {
|
|
69
115
|
const {slug: _slug, ...withoutSlug} = validConnectionAvailable;
|
|
70
116
|
|
package/src/events.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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;
|
|
4
5
|
export const INTEGRATION_CONNECTION_AVAILABLE = 'integrations.connection.available' as const;
|
|
@@ -28,6 +29,11 @@ export const integrationConnectionAvailableSchema = z.object({
|
|
|
28
29
|
workspaceId: nonEmptyStringSchema,
|
|
29
30
|
connectionId: nonEmptyStringSchema,
|
|
30
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([]),
|
|
31
37
|
});
|
|
32
38
|
export type IntegrationConnectionAvailableEvent = z.infer<
|
|
33
39
|
typeof integrationConnectionAvailableSchema
|
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)
|