@checkstack/integration-backend 0.0.2
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 +85 -0
- package/drizzle/0000_glossy_red_hulk.sql +28 -0
- package/drizzle/0001_rich_fixer.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +191 -0
- package/drizzle/meta/0001_snapshot.json +190 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +8 -0
- package/package.json +36 -0
- package/src/connection-store.test.ts +468 -0
- package/src/connection-store.ts +463 -0
- package/src/delivery-coordinator.ts +390 -0
- package/src/event-registry.test.ts +396 -0
- package/src/event-registry.ts +99 -0
- package/src/hook-subscriber.ts +104 -0
- package/src/index.ts +306 -0
- package/src/provider-registry.test.ts +314 -0
- package/src/provider-registry.ts +107 -0
- package/src/provider-types.ts +257 -0
- package/src/router.ts +858 -0
- package/src/schema.ts +78 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { PluginMetadata } from "@checkstack/common";
|
|
2
|
+
import { toJsonSchema } from "@checkstack/backend-api";
|
|
3
|
+
import type {
|
|
4
|
+
IntegrationProvider,
|
|
5
|
+
RegisteredIntegrationProvider,
|
|
6
|
+
} from "./provider-types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Registry for integration providers.
|
|
10
|
+
* Plugins register their providers here to enable webhook delivery to external systems.
|
|
11
|
+
*/
|
|
12
|
+
export interface IntegrationProviderRegistry {
|
|
13
|
+
/**
|
|
14
|
+
* Register an integration provider.
|
|
15
|
+
* Called via the extension point during plugin registration.
|
|
16
|
+
*/
|
|
17
|
+
register(
|
|
18
|
+
provider: IntegrationProvider<unknown>,
|
|
19
|
+
pluginMetadata: PluginMetadata
|
|
20
|
+
): void;
|
|
21
|
+
|
|
22
|
+
/** Get all registered providers */
|
|
23
|
+
getProviders(): RegisteredIntegrationProvider<unknown>[];
|
|
24
|
+
|
|
25
|
+
/** Get a specific provider by its fully qualified ID */
|
|
26
|
+
getProvider(
|
|
27
|
+
qualifiedId: string
|
|
28
|
+
): RegisteredIntegrationProvider<unknown> | undefined;
|
|
29
|
+
|
|
30
|
+
/** Check if a provider is registered */
|
|
31
|
+
hasProvider(qualifiedId: string): boolean;
|
|
32
|
+
|
|
33
|
+
/** Get the JSON Schema for a provider's config */
|
|
34
|
+
getProviderConfigSchema(
|
|
35
|
+
qualifiedId: string
|
|
36
|
+
): Record<string, unknown> | undefined;
|
|
37
|
+
|
|
38
|
+
/** Get the JSON Schema for a provider's connection config (if any) */
|
|
39
|
+
getProviderConnectionSchema(
|
|
40
|
+
qualifiedId: string
|
|
41
|
+
): Record<string, unknown> | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a new integration provider registry instance.
|
|
46
|
+
*/
|
|
47
|
+
export function createIntegrationProviderRegistry(): IntegrationProviderRegistry {
|
|
48
|
+
const providers = new Map<string, RegisteredIntegrationProvider<unknown>>();
|
|
49
|
+
const configSchemas = new Map<string, Record<string, unknown>>();
|
|
50
|
+
const connectionSchemas = new Map<string, Record<string, unknown>>();
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
register(
|
|
54
|
+
provider: IntegrationProvider<unknown>,
|
|
55
|
+
pluginMetadata: PluginMetadata
|
|
56
|
+
): void {
|
|
57
|
+
const qualifiedId = `${pluginMetadata.pluginId}.${provider.id}`;
|
|
58
|
+
|
|
59
|
+
const registered: RegisteredIntegrationProvider<unknown> = {
|
|
60
|
+
...provider,
|
|
61
|
+
qualifiedId,
|
|
62
|
+
ownerPluginId: pluginMetadata.pluginId,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
providers.set(qualifiedId, registered);
|
|
66
|
+
|
|
67
|
+
// Convert the provider's config schema to JSON Schema for UI
|
|
68
|
+
// Uses the platform's toJsonSchema which handles secrets/colors
|
|
69
|
+
const jsonSchema = toJsonSchema(provider.config.schema);
|
|
70
|
+
configSchemas.set(qualifiedId, jsonSchema);
|
|
71
|
+
|
|
72
|
+
// Also convert connection schema if present
|
|
73
|
+
if (provider.connectionSchema) {
|
|
74
|
+
const connectionJsonSchema = toJsonSchema(
|
|
75
|
+
provider.connectionSchema.schema
|
|
76
|
+
);
|
|
77
|
+
connectionSchemas.set(qualifiedId, connectionJsonSchema);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
getProviders(): RegisteredIntegrationProvider<unknown>[] {
|
|
82
|
+
return [...providers.values()];
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
getProvider(
|
|
86
|
+
qualifiedId: string
|
|
87
|
+
): RegisteredIntegrationProvider<unknown> | undefined {
|
|
88
|
+
return providers.get(qualifiedId);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
hasProvider(qualifiedId: string): boolean {
|
|
92
|
+
return providers.has(qualifiedId);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
getProviderConfigSchema(
|
|
96
|
+
qualifiedId: string
|
|
97
|
+
): Record<string, unknown> | undefined {
|
|
98
|
+
return configSchemas.get(qualifiedId);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
getProviderConnectionSchema(
|
|
102
|
+
qualifiedId: string
|
|
103
|
+
): Record<string, unknown> | undefined {
|
|
104
|
+
return connectionSchemas.get(qualifiedId);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Provider Types
|
|
3
|
+
*
|
|
4
|
+
* These types define the contract for integration provider plugins.
|
|
5
|
+
* All backend-only types live here - frontend uses Zod schemas from integration-common.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import type { Versioned, Logger, Hook } from "@checkstack/backend-api";
|
|
9
|
+
import type { LucideIconName } from "@checkstack/common";
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Integration Event Definition Types
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Metadata for registering a hook as an integration event.
|
|
17
|
+
* Plugins use this to expose their hooks for external webhook subscriptions.
|
|
18
|
+
*/
|
|
19
|
+
export interface IntegrationEventDefinition<T = unknown> {
|
|
20
|
+
/** The hook to expose (from the owning plugin) */
|
|
21
|
+
hook: Hook<T>;
|
|
22
|
+
|
|
23
|
+
/** Human-readable name for the UI */
|
|
24
|
+
displayName: string;
|
|
25
|
+
|
|
26
|
+
/** Description of when this event fires */
|
|
27
|
+
description?: string;
|
|
28
|
+
|
|
29
|
+
/** Category for UI grouping (e.g., "Health", "Incidents", "Maintenance") */
|
|
30
|
+
category?: string;
|
|
31
|
+
|
|
32
|
+
/** Zod schema for the payload (used for UI preview and validation) */
|
|
33
|
+
payloadSchema: z.ZodType<T>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Optional: Transform hook payload before sending to webhooks.
|
|
37
|
+
* Use this to enrich the payload with additional context or
|
|
38
|
+
* redact sensitive fields.
|
|
39
|
+
*/
|
|
40
|
+
transformPayload?: (payload: T) => Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Integration Provider Types
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Context passed to the provider's deliver() method.
|
|
49
|
+
*/
|
|
50
|
+
export interface IntegrationDeliveryContext<TConfig = unknown> {
|
|
51
|
+
event: {
|
|
52
|
+
/** Fully qualified event ID */
|
|
53
|
+
eventId: string;
|
|
54
|
+
/** Event payload (possibly transformed) */
|
|
55
|
+
payload: Record<string, unknown>;
|
|
56
|
+
/** ISO timestamp when the event was emitted */
|
|
57
|
+
timestamp: string;
|
|
58
|
+
/** Unique ID for this delivery attempt */
|
|
59
|
+
deliveryId: string;
|
|
60
|
+
};
|
|
61
|
+
subscription: {
|
|
62
|
+
/** Subscription ID */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Subscription name */
|
|
65
|
+
name: string;
|
|
66
|
+
};
|
|
67
|
+
/** Provider-specific configuration */
|
|
68
|
+
providerConfig: TConfig;
|
|
69
|
+
/** Scoped logger for delivery tracing */
|
|
70
|
+
logger: Logger;
|
|
71
|
+
/**
|
|
72
|
+
* Get connection credentials by ID (for providers with connectionSchema).
|
|
73
|
+
* Only available when provider has a connectionSchema defined.
|
|
74
|
+
*/
|
|
75
|
+
getConnectionWithCredentials?: (
|
|
76
|
+
connectionId: string
|
|
77
|
+
) => Promise<{ config: Record<string, unknown> } | undefined>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Result of a provider delivery attempt.
|
|
82
|
+
*/
|
|
83
|
+
export interface IntegrationDeliveryResult {
|
|
84
|
+
success: boolean;
|
|
85
|
+
/** External ID returned by the target system (e.g., Jira issue key) */
|
|
86
|
+
externalId?: string;
|
|
87
|
+
/** Error message if delivery failed */
|
|
88
|
+
error?: string;
|
|
89
|
+
/** Milliseconds to wait before retrying (if applicable) */
|
|
90
|
+
retryAfterMs?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Result of testing a provider connection.
|
|
95
|
+
*/
|
|
96
|
+
export interface TestConnectionResult {
|
|
97
|
+
success: boolean;
|
|
98
|
+
message?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Documentation that helps users implement their endpoint for this provider.
|
|
103
|
+
*/
|
|
104
|
+
export interface ProviderDocumentation {
|
|
105
|
+
/** Brief setup instructions (rendered as markdown) */
|
|
106
|
+
setupGuide?: string;
|
|
107
|
+
/** Example request body (JSON string for syntax highlighting) */
|
|
108
|
+
examplePayload?: string;
|
|
109
|
+
/** HTTP headers that will be sent with each request */
|
|
110
|
+
headers?: Array<{ name: string; description: string }>;
|
|
111
|
+
/** Link to external documentation */
|
|
112
|
+
externalDocsUrl?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Option returned by getConnectionOptions for dynamic dropdowns.
|
|
117
|
+
*/
|
|
118
|
+
export interface ConnectionOption {
|
|
119
|
+
value: string;
|
|
120
|
+
label: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parameters for getConnectionOptions method.
|
|
126
|
+
*/
|
|
127
|
+
export interface GetConnectionOptionsParams {
|
|
128
|
+
/** The connection ID to use for fetching options */
|
|
129
|
+
connectionId: string;
|
|
130
|
+
/** Name of the resolver (matches x-options-resolver in schema) */
|
|
131
|
+
resolverName: string;
|
|
132
|
+
/** Current form values for dependent fields */
|
|
133
|
+
context: Record<string, unknown>;
|
|
134
|
+
/** Logger for logging */
|
|
135
|
+
logger: Logger;
|
|
136
|
+
/**
|
|
137
|
+
* Get connection credentials by ID.
|
|
138
|
+
* Provided by the integration backend for providers to access connection config.
|
|
139
|
+
*/
|
|
140
|
+
getConnectionWithCredentials: (
|
|
141
|
+
connectionId: string
|
|
142
|
+
) => Promise<{ config: Record<string, unknown> } | undefined>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Integration provider definition.
|
|
147
|
+
* Providers define how to deliver events to specific external systems.
|
|
148
|
+
*
|
|
149
|
+
* @template TConfig - Per-subscription configuration type
|
|
150
|
+
* @template TConnection - Site-wide connection configuration type (optional)
|
|
151
|
+
*/
|
|
152
|
+
export interface IntegrationProvider<
|
|
153
|
+
TConfig = unknown,
|
|
154
|
+
TConnection = undefined
|
|
155
|
+
> {
|
|
156
|
+
/** Local identifier, namespaced on registration to {pluginId}.{id} */
|
|
157
|
+
id: string;
|
|
158
|
+
|
|
159
|
+
/** Display name for UI */
|
|
160
|
+
displayName: string;
|
|
161
|
+
|
|
162
|
+
/** Description of what this provider does */
|
|
163
|
+
description?: string;
|
|
164
|
+
|
|
165
|
+
/** Lucide icon name in PascalCase (e.g., 'Webhook') */
|
|
166
|
+
icon?: LucideIconName;
|
|
167
|
+
|
|
168
|
+
/** Per-subscription configuration schema */
|
|
169
|
+
config: Versioned<TConfig>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Optional site-wide connection schema.
|
|
173
|
+
* When provided, the platform will:
|
|
174
|
+
* - Store connections centrally via ConfigService
|
|
175
|
+
* - Show a "Connections" management UI
|
|
176
|
+
* - Add a connection dropdown to subscription config
|
|
177
|
+
*/
|
|
178
|
+
connectionSchema?: Versioned<TConnection>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Events this provider can handle.
|
|
182
|
+
* If undefined, provider accepts all events.
|
|
183
|
+
* Event IDs are fully qualified: {pluginId}.{hookId}
|
|
184
|
+
*/
|
|
185
|
+
supportedEvents?: string[];
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Optional documentation to help users configure their endpoints.
|
|
189
|
+
* Displayed in the UI when creating/editing subscriptions.
|
|
190
|
+
*/
|
|
191
|
+
documentation?: ProviderDocumentation;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Transform and deliver the event to the external system.
|
|
195
|
+
*/
|
|
196
|
+
deliver(
|
|
197
|
+
context: IntegrationDeliveryContext<TConfig>
|
|
198
|
+
): Promise<IntegrationDeliveryResult>;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Optional: Test the connection configuration.
|
|
202
|
+
* Called when admin clicks "Test Connection" in the connections UI.
|
|
203
|
+
* Only applicable when connectionSchema is defined.
|
|
204
|
+
*/
|
|
205
|
+
testConnection?(config: TConnection): Promise<TestConnectionResult>;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Optional: Fetch dynamic options for cascading dropdowns.
|
|
209
|
+
* Called when subscription config has fields with x-options-resolver.
|
|
210
|
+
* Only applicable when connectionSchema is defined.
|
|
211
|
+
*/
|
|
212
|
+
getConnectionOptions?(
|
|
213
|
+
params: GetConnectionOptionsParams
|
|
214
|
+
): Promise<ConnectionOption[]>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Registered provider with full namespace information.
|
|
219
|
+
*/
|
|
220
|
+
export interface RegisteredIntegrationProvider<
|
|
221
|
+
TConfig = unknown,
|
|
222
|
+
TConnection = unknown
|
|
223
|
+
> extends IntegrationProvider<TConfig, TConnection> {
|
|
224
|
+
/** Fully qualified ID: {pluginId}.{id} */
|
|
225
|
+
qualifiedId: string;
|
|
226
|
+
|
|
227
|
+
/** Plugin that registered this provider */
|
|
228
|
+
ownerPluginId: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Registered integration event with full namespace information.
|
|
233
|
+
*/
|
|
234
|
+
export interface RegisteredIntegrationEvent<T = unknown> {
|
|
235
|
+
/** Fully qualified event ID: {pluginId}.{hookId} */
|
|
236
|
+
eventId: string;
|
|
237
|
+
|
|
238
|
+
/** Original hook reference */
|
|
239
|
+
hook: Hook<T>;
|
|
240
|
+
|
|
241
|
+
/** Plugin that registered this event */
|
|
242
|
+
ownerPluginId: string;
|
|
243
|
+
|
|
244
|
+
/** UI metadata */
|
|
245
|
+
displayName: string;
|
|
246
|
+
description?: string;
|
|
247
|
+
category?: string;
|
|
248
|
+
|
|
249
|
+
/** JSON Schema for payload (derived from Zod) */
|
|
250
|
+
payloadJsonSchema: Record<string, unknown>;
|
|
251
|
+
|
|
252
|
+
/** Original Zod schema */
|
|
253
|
+
payloadSchema: z.ZodType<T>;
|
|
254
|
+
|
|
255
|
+
/** Optional payload transformer */
|
|
256
|
+
transformPayload?: (payload: T) => Record<string, unknown>;
|
|
257
|
+
}
|